Create a message dialog in Android via NDK callback - java

I have an android app in which I've set up an OpenGL context on the Java side, and am sending drawing from the NDK/C++ side. This all seems to work well.
I want the C++ side to be able to pop up a dialog. I implemented a java MakeADialog function which is getting fired just fine from the C side via a env->CallVoidMethod(javaClass, javaMethod); My Java-side receiving function looks like this:
public static void MakeADialog() {
Log.w("title", "MakeADialog fired!");
}
This is in a separate class (not an Activity or Runnable). The above works fine and I can see my MakeADialg log message. However, when I try to create an actual dialog box, then I get lots of crashes. I can tell I'm not grocking what 'thread' I'm running on when I call from the C side into the Java side. It appears I'm getting into trouble when I try to create a new thread/dialog.
I've tried lots of the suggestions on here about creating a Runnable, Thread, etc - but they always seem to give me the dreaded 'Can't create handler inside thread that has not called Looper.prepare()' or that the parent is null for the view. Most of these methods revolve around storing the Activity and Context pointers as static and retrieving them via get functions when I'm in the MakeADialog callback.
AlertDialog alertDialog = new AlertDialog.Builder(MyApp.GetMyContext()).create(); where the GetMyContext() function simply returns the this pointer of the main activity creation thread that I stored during app startup.
Has anyone popped up a dialog launched from their NDK side or can point me to some relevant docs that will help me understand how to create a new dialog from an NDK callback?
Thanks in advance!

Maybe we can use the example from gist to create a modal dialog. I suspected that You called it from a worker thread so "they always seem to give me the dreaded 'Can't create handler inside thread that has not called Looper.prepare()' or that the parent is null for the view." (see also Can't create handler inside thread that has not called Looper.prepare()).
The key code based on the official example Native Activity and the gist code:
On the Java side,
package ss.fang.brickgo;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.NativeActivity;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
public class GameActivity extends NativeActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
/**
* This function will be called from C++ by name and signature (Ljava/lang/String;Z)I)
*
* #param message the message text to show
* #param model if true, it will block the current thread, otherwise, it acts like a modeless dialog.
* #return return id of the button that was clicked for a model dialog, otherwise, 0.
* #see #showAlertCallback
* #see <a href="https://stackoverflow.com/questions/11730001/create-a-message-dialog-in-android-via-ndk-callback/60611870#60611870">
* Create a message dialog in Android via NDK callback</a>
* #see <a href="https://stackoverflow.com/questions/6120567/android-how-to-get-a-modal-dialog-or-similar-modal-behavior">
* Android: How to get a modal dialog or similar modal behavior?</a>
*/
public int showAlert(final String message, boolean model) {
//https://stackoverflow.com/questions/11411022/how-to-check-if-current-thread-is-not-main-thread
if (Looper.myLooper() == Looper.getMainLooper() && model) {
// Current Thread is UI Thread. Looper.getMainLooper().isCurrentThread()
//android.os.NetworkOnMainThreadException
throw new RuntimeException("Can't create a model dialog inside Main thread");
}
ApplicationInfo applicationInfo = getApplicationInfo();
final CharSequence appName = getPackageManager().getApplicationLabel(applicationInfo);
// Use a semaphore to create a modal dialog. Also, it's holden by the dialog's listener.
final Semaphore semaphore = model ? new Semaphore(0, true) : null;
// The button that was clicked (ex. BUTTON_POSITIVE) or the position of the item clicked
final AtomicInteger buttonId = new AtomicInteger();
this.runOnUiThread(new Runnable() {
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this, AlertDialog.THEME_HOLO_DARK);
builder.setTitle(appName);
builder.setMessage(message);
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
buttonId.set(id);
if (null != semaphore)
semaphore.release();
else
showAlertCallback(id);
if (DialogInterface.BUTTON_POSITIVE == id) {
GameActivity.this.finish();
}
}
};
builder.setNegativeButton(android.R.string.cancel, listener);
builder.setPositiveButton(android.R.string.ok, listener);
builder.setCancelable(false);
AlertDialog dialog = builder.create();
dialog.show();
}
});
if (null != semaphore)
try {
semaphore.acquire();
} catch (InterruptedException e) {
Log.v("GameActivity", "ignored", e);
}
return buttonId.get();
}
/**
* The callback for showAlert when it acts like a modeless dialog
*
* #param id the button that was clicked
*/
public native void showAlertCallback(int id);
/**
* #see <a href="https://stackoverflow.com/questions/13822842/dialogfragment-with-clear-background-not-dimmed">
* DialogFragment with clear background (not dimmed)</a>
*/
protected void showDialog() {
Dialog dialog = new Dialog(this);
dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// layout to display
dialog.setContentView(R.layout.dialog_layout);
// set color transpartent
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.show();
}
}
On the Native (C++11) side,
/** #param onClickListener MUST be NULL because the model dialog is not implemented. */
typedef void *(OnClickListener)(int id);
jint showAlert(struct android_app *state, const char *message, bool model = false);
/** Process the next input event. */
static int32_t engine_handle_input(struct android_app *app, AInputEvent *event) {
auto *engine = (struct engine *) app->userData;
auto type = AInputEvent_getType(event);
if (AINPUT_EVENT_TYPE_MOTION == type) {
engine->animating = 1;
engine->state.x = AMotionEvent_getX(event, 0);
engine->state.y = AMotionEvent_getY(event, 0);
return 1;
} else if (AINPUT_EVENT_TYPE_KEY == type) {
auto action = AKeyEvent_getAction(event);
if (AKEY_EVENT_ACTION_DOWN == action && AKEYCODE_BACK == AKeyEvent_getKeyCode(event)) {
//https://stackoverflow.com/questions/15913080/crash-when-closing-soft-keyboard-while-using-native-activity
// skip predispatch (all it does is send to the IME)
//if (!AInputQueue_preDispatchEvent(app->inputQueue, event))
//int32_t handled = 0;
//if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event);
//AInputQueue_finishEvent(app->inputQueue, event, handled);
LOGI("Before showAlert in modal behavior, it blocks until the dialog dismisses.");
showAlert(app, "Go Back?", true);
LOGI("After showAlert in modal behavior, the dialog should have been dismissed.");
return 1;
}
}
return 0; //not handled
}
/** #return the id of the button clicked if model is true, or 0 */
jint showAlert(struct android_app *state, const char *message, bool model /* = false */) {
JNIEnv *jni = NULL;
state->activity->vm->AttachCurrentThread(&jni, NULL);
jclass clazz = jni->GetObjectClass(state->activity->clazz);
// Get the ID of the method we want to call
// This must match the name and signature from the Java side Signature has to match java
// implementation (second string hints a java string parameter)
jmethodID methodID = jni->GetMethodID(clazz, "showAlert", "(Ljava/lang/String;Z)I");
// Strings passed to the function need to be converted to a java string object
jstring jmessage = jni->NewStringUTF(message);
jint result = jni->CallIntMethod(state->activity->clazz, methodID, jmessage, model);
// Remember to clean up passed values
jni->DeleteLocalRef(jmessage);
state->activity->vm->DetachCurrentThread();
return result;
}
extern "C"
JNIEXPORT void JNICALL
Java_ss_fang_brickgo_GameActivity_showAlertCallback(JNIEnv *env, jobject thiz, jint id) {
LOGI("showAlertCallback %d", id);
}

Related

Android the views are reset by orientation the screen [duplicate]

My program does some network activity in a background thread. Before starting, it pops up a progress dialog. The dialog is dismissed on the handler. This all works fine, except when screen orientation changes while the dialog is up (and the background thread is going). At this point the app either crashes, or deadlocks, or gets into a weird stage where the app does not work at all until all the threads have been killed.
How can I handle the screen orientation change gracefully?
The sample code below matches roughly what my real program does:
public class MyAct extends Activity implements Runnable {
public ProgressDialog mProgress;
// UI has a button that when pressed calls send
public void send() {
mProgress = ProgressDialog.show(this, "Please wait",
"Please wait",
true, true);
Thread thread = new Thread(this);
thread.start();
}
public void run() {
Thread.sleep(10000);
Message msg = new Message();
mHandler.sendMessage(msg);
}
private final Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
mProgress.dismiss();
}
};
}
Stack:
E/WindowManager( 244): Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView#433b7150 that was originally added here
E/WindowManager( 244): android.view.WindowLeaked: Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView#433b7150 that was originally added here
E/WindowManager( 244): at android.view.ViewRoot.<init>(ViewRoot.java:178)
E/WindowManager( 244): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:147)
E/WindowManager( 244): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
E/WindowManager( 244): at android.view.Window$LocalWindowManager.addView(Window.java:393)
E/WindowManager( 244): at android.app.Dialog.show(Dialog.java:212)
E/WindowManager( 244): at android.app.ProgressDialog.show(ProgressDialog.java:103)
E/WindowManager( 244): at android.app.ProgressDialog.show(ProgressDialog.java:91)
E/WindowManager( 244): at MyAct.send(MyAct.java:294)
E/WindowManager( 244): at MyAct$4.onClick(MyAct.java:174)
E/WindowManager( 244): at android.view.View.performClick(View.java:2129)
E/WindowManager( 244): at android.view.View.onTouchEvent(View.java:3543)
E/WindowManager( 244): at android.widget.TextView.onTouchEvent(TextView.java:4664)
E/WindowManager( 244): at android.view.View.dispatchTouchEvent(View.java:3198)
I have tried to dismiss the progress dialog in onSaveInstanceState, but that just prevents an immediate crash. The background thread is still going, and the UI is in partially drawn state. Need to kill the whole app before it starts working again.
Edit: Google engineers do not recommend this approach, as described by Dianne Hackborn (a.k.a. hackbod) in this StackOverflow post. Check out this blog post for more information.
You have to add this to the activity declaration in the manifest:
android:configChanges="orientation|screenSize"
so it looks like
<activity android:label="#string/app_name"
android:configChanges="orientation|screenSize|keyboardHidden"
android:name=".your.package">
The matter is that the system destroys the activity when a change in the configuration occurs. See ConfigurationChanges.
So putting that in the configuration file avoids the system to destroy your activity. Instead it invokes the onConfigurationChanged(Configuration) method.
When you switch orientations, Android will create a new View. You're probably getting crashes because your background thread is trying to change the state on the old one. (It may also be having trouble because your background thread isn't on the UI thread)
I'd suggest making that mHandler volatile and updating it when the orientation changes.
I came up with a rock-solid solution for these issues that conforms with the 'Android Way' of things. I have all my long-running operations using the IntentService pattern.
That is, my activities broadcast intents, the IntentService does the work, saves the data in the DB and then broadcasts sticky intents. The sticky part is important, such that even if the Activity was paused during during the time after the user initiated the work and misses the real time broadcast from the IntentService we can still respond and pick up the data from the calling Activity. ProgressDialogs can work with this pattern quite nicely with onSaveInstanceState().
Basically, you need to save a flag that you have a progress dialog running in the saved instance bundle. Do not save the progress dialog object because this will leak the entire Activity. To have a persistent handle to the progress dialog, I store it as a weak reference in the application object. On orientation change or anything else that causes the Activity to pause (phone call, user hits home etc.) and then resume, I dismiss the old dialog and recreate a new dialog in the newly created Activity.
For indefinite progress dialogs this is easy. For progress bar style, you have to put the last known progress in the bundle and whatever information you're using locally in the activity to keep track of the progress. On restoring the progress, you'll use this information to re-spawn the progress bar in the same state as before and then update based on the current state of things.
So to summarize, putting long-running tasks into an IntentService coupled with judicious use of onSaveInstanceState() allows you to efficiently keep track of dialogs and restore then across the Activity life-cycle events. Relevant bits of Activity code are below. You'll also need logic in your BroadcastReceiver to handle Sticky intents appropriately, but that is beyond the scope of this.
public void doSignIn(View view) {
waiting=true;
AppClass app=(AppClass) getApplication();
String logingon=getString(R.string.signon);
app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
...
}
#Override
protected void onSaveInstanceState(Bundle saveState) {
super.onSaveInstanceState(saveState);
saveState.putBoolean("waiting",waiting);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!=null) {
restoreProgress(savedInstanceState);
}
...
}
private void restoreProgress(Bundle savedInstanceState) {
waiting=savedInstanceState.getBoolean("waiting");
if (waiting) {
AppClass app=(AppClass) getApplication();
ProgressDialog refresher=(ProgressDialog) app.Dialog.get();
refresher.dismiss();
String logingon=getString(R.string.signon);
app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
}
}
I met the same problem. My activity needs to parse some data from a URL and it's slow. So I create a thread to do so, then show a progress dialog. I let the thread post a message back to UI thread via Handler when it's finished. In Handler.handleMessage, I get the data object (ready now) from thread and populate it to UI. So it's very similar to your example.
After a lot of trial and error it looks like I found a solution. At least now I can rotate screen at any moment, before or after the thread is done. In all tests, the dialog is properly closed and all behaviors are as expected.
What I did is shown below. The goal is to fill my data model (mDataObject) and then populate it to UI. Should allow screen rotation at any moment without surprise.
class MyActivity {
private MyDataObject mDataObject = null;
private static MyThread mParserThread = null; // static, or make it singleton
OnCreate() {
...
Object retained = this.getLastNonConfigurationInstance();
if(retained != null) {
// data is already completely obtained before config change
// by my previous self.
// no need to create thread or show dialog at all
mDataObject = (MyDataObject) retained;
populateUI();
} else if(mParserThread != null && mParserThread.isAlive()){
// note: mParserThread is a static member or singleton object.
// config changed during parsing in previous instance. swap handler
// then wait for it to finish.
mParserThread.setHandler(new MyHandler());
} else {
// no data and no thread. likely initial run
// create thread, show dialog
mParserThread = new MyThread(..., new MyHandler());
mParserThread.start();
showDialog(DIALOG_PROGRESS);
}
}
// http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html
public Object onRetainNonConfigurationInstance() {
// my future self can get this without re-downloading
// if it's already ready.
return mDataObject;
}
// use Activity.showDialog instead of ProgressDialog.show
// so the dialog can be automatically managed across config change
#Override
protected Dialog onCreateDialog(int id) {
// show progress dialog here
}
// inner class of MyActivity
private class MyHandler extends Handler {
public void handleMessage(msg) {
mDataObject = mParserThread.getDataObject();
populateUI();
dismissDialog(DIALOG_PROGRESS);
}
}
}
class MyThread extends Thread {
Handler mHandler;
MyDataObject mDataObject;
// constructor with handler param
public MyHandler(..., Handler h) {
...
mHandler = h;
}
public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change
public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller
public void run() {
mDataObject = new MyDataObject();
// do the lengthy task to fill mDataObject with data
lengthyTask(mDataObject);
// done. notify activity
mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data.
}
}
That's what works for me. I don't know if this is the "correct" method as designed by Android -- they claim this "destroy/recreate activity during screen rotation" actually makes things easier, so I guess it shouldn't be too tricky.
Let me know if you see a problem in my code. As said above I don't really know if there is any side effect.
The original perceived problem was that the code would not survive a screen orientation change. Apparently this was "solved" by having the program handle the screen orientation change itself, instead of letting the UI framework do it (via calling onDestroy)).
I would submit that if the underlying problem is that the program will not survive onDestroy(), then the accepted solution is just a workaround that leaves the program with serious other problems and vulnerabilities. Remember that the Android framework specifically states that your activity is at risk for being destroyed almost at any time due to circumstances outside your control. Therefore, your activity must be able to survive onDestroy() and subsequent onCreate() for any reason, not just a screen orientation change.
If you are going to accept handling screen orientation changes yourself to solve the OP's problem, you need to verify that other causes of onDestroy() do not result in the same error. Are you able to do this? If not, I would question whether the "accepted" answer is really a very good one.
My solution was to extend the ProgressDialog class to get my own MyProgressDialog.
I redefined show() and dismiss() methods to lock the orientation before showing the Dialog and unlock it back when Dialog is dismissed.
So when the Dialog is shown and the orientation of the device changes, the orientation of the screen remains until dismiss() is called, then screen-orientation changes according to sensor-values/device-orientation.
Here is my code:
public class MyProgressDialog extends ProgressDialog {
private Context mContext;
public MyProgressDialog(Context context) {
super(context);
mContext = context;
}
public MyProgressDialog(Context context, int theme) {
super(context, theme);
mContext = context;
}
public void show() {
if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
else
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
super.show();
}
public void dismiss() {
super.dismiss();
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
}
I faced this same problem, and I came up with a solution that didn't invole using the ProgressDialog and I get faster results.
What I did was create a layout that has a ProgressBar in it.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ProgressBar
android:id="#+id/progressImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
/>
</RelativeLayout>
Then in the onCreate method do the following
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.progress);
}
Then do the long task in a thread, and when that's finished have a Runnable set the content view to the real layout you want to use for this activity.
For example:
mHandler.post(new Runnable(){
public void run() {
setContentView(R.layout.my_layout);
}
});
This is what I did, and I've found that it runs faster than showing the ProgressDialog and it's less intrusive and has a better look in my opinion.
However, if you're wanting to use the ProgressDialog, then this answer isn't for you.
I discovered a solution to this that I haven't yet seen elsewhere. You can use a custom application object that knows if you have background tasks going, instead of trying to do this in the activity that gets destroyed and recreated on orientation change. I blogged about this in here.
I going to contribute my approach to handling this rotation issue. This may not be relevant to OP as he's not using AsyncTask, but maybe others will find it useful. It's pretty simple but it seems to do the job for me:
I have a login activity with a nested AsyncTask class called BackgroundLoginTask.
In my BackgroundLoginTask I don't do anything out of the ordinary except to add a null check upon calling ProgressDialog's dismiss:
#Override
protected void onPostExecute(Boolean result)
{
if (pleaseWaitDialog != null)
pleaseWaitDialog.dismiss();
[...]
}
This is to handle the case where the background task finishes while the Activity is not visible and, therefore, the progress dialog has already been dismissed by the onPause() method.
Next, in my parent Activity class, I create global static handles to my AsyncTask class and my ProgressDialog (the AsyncTask, being nested, can access these variables):
private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;
This serves two purposes: First, it allows my Activity to always access the AsyncTask object even from a new, post-rotated activity. Second, it allows my BackgroundLoginTask to access and dismiss the ProgressDialog even after a rotate.
Next, I add this to onPause(), causing the progress dialog to disappear when our Activity is leaving the foreground (preventing that ugly "force close" crash):
if (pleaseWaitDialog != null)
pleaseWaitDialog.dismiss();
Finally, I have the following in my onResume() method:
if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
{
if (pleaseWaitDialog != null)
pleaseWaitDialog.show();
}
This allows the Dialog to reappear after the Activity is recreated.
Here is the entire class:
public class NSFkioskLoginActivity extends NSFkioskBaseActivity {
private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;
private Controller cont;
// This is the app entry point.
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (CredentialsAvailableAndValidated())
{
//Go to main menu and don't run rest of onCreate method.
gotoMainMenu();
return;
}
setContentView(R.layout.login);
populateStoredCredentials();
}
//Save current progress to options when app is leaving foreground
#Override
public void onPause()
{
super.onPause();
saveCredentialsToPreferences(false);
//Get rid of progress dialog in the event of a screen rotation. Prevents a crash.
if (pleaseWaitDialog != null)
pleaseWaitDialog.dismiss();
}
#Override
public void onResume()
{
super.onResume();
if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
{
if (pleaseWaitDialog != null)
pleaseWaitDialog.show();
}
}
/**
* Go to main menu, finishing this activity
*/
private void gotoMainMenu()
{
startActivity(new Intent(getApplicationContext(), NSFkioskMainMenuActivity.class));
finish();
}
/**
*
* #param setValidatedBooleanTrue If set true, method will set CREDS_HAVE_BEEN_VALIDATED to true in addition to saving username/password.
*/
private void saveCredentialsToPreferences(boolean setValidatedBooleanTrue)
{
SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE);
SharedPreferences.Editor prefEditor = settings.edit();
EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
EditText pswText = (EditText) findViewById(R.id.editTextPassword);
prefEditor.putString(USERNAME, usernameText.getText().toString());
prefEditor.putString(PASSWORD, pswText.getText().toString());
if (setValidatedBooleanTrue)
prefEditor.putBoolean(CREDS_HAVE_BEEN_VALIDATED, true);
prefEditor.commit();
}
/**
* Checks if user is already signed in
*/
private boolean CredentialsAvailableAndValidated() {
SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
MODE_PRIVATE);
if (settings.contains(USERNAME) && settings.contains(PASSWORD) && settings.getBoolean(CREDS_HAVE_BEEN_VALIDATED, false) == true)
return true;
else
return false;
}
//Populate stored credentials, if any available
private void populateStoredCredentials()
{
SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
MODE_PRIVATE);
settings.getString(USERNAME, "");
EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
usernameText.setText(settings.getString(USERNAME, ""));
EditText pswText = (EditText) findViewById(R.id.editTextPassword);
pswText.setText(settings.getString(PASSWORD, ""));
}
/**
* Validate credentials in a seperate thread, displaying a progress circle in the meantime
* If successful, save credentials in preferences and proceed to main menu activity
* If not, display an error message
*/
public void loginButtonClick(View view)
{
if (phoneIsOnline())
{
EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
EditText pswText = (EditText) findViewById(R.id.editTextPassword);
//Call background task worker with username and password params
backgroundLoginTask = new BackgroundLoginTask();
backgroundLoginTask.execute(usernameText.getText().toString(), pswText.getText().toString());
}
else
{
//Display toast informing of no internet access
String notOnlineMessage = getResources().getString(R.string.noNetworkAccessAvailable);
Toast toast = Toast.makeText(getApplicationContext(), notOnlineMessage, Toast.LENGTH_SHORT);
toast.show();
}
}
/**
*
* Takes two params: username and password
*
*/
public class BackgroundLoginTask extends AsyncTask<Object, String, Boolean>
{
private Exception e = null;
#Override
protected void onPreExecute()
{
cont = Controller.getInstance();
//Show progress dialog
String pleaseWait = getResources().getString(R.string.pleaseWait);
String commWithServer = getResources().getString(R.string.communicatingWithServer);
if (pleaseWaitDialog == null)
pleaseWaitDialog= ProgressDialog.show(NSFkioskLoginActivity.this, pleaseWait, commWithServer, true);
}
#Override
protected Boolean doInBackground(Object... params)
{
try {
//Returns true if credentials were valid. False if not. Exception if server could not be reached.
return cont.validateCredentials((String)params[0], (String)params[1]);
} catch (Exception e) {
this.e=e;
return false;
}
}
/**
* result is passed from doInBackground. Indicates whether credentials were validated.
*/
#Override
protected void onPostExecute(Boolean result)
{
//Hide progress dialog and handle exceptions
//Progress dialog may be null if rotation has been switched
if (pleaseWaitDialog != null)
{
pleaseWaitDialog.dismiss();
pleaseWaitDialog = null;
}
if (e != null)
{
//Show toast with exception text
String networkError = getResources().getString(R.string.serverErrorException);
Toast toast = Toast.makeText(getApplicationContext(), networkError, Toast.LENGTH_SHORT);
toast.show();
}
else
{
if (result == true)
{
saveCredentialsToPreferences(true);
gotoMainMenu();
}
else
{
String toastText = getResources().getString(R.string.invalidCredentialsEntered);
Toast toast = Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT);
toast.show();
}
}
}
}
}
I am by no means a seasoned Android developer, so feel free to comment.
Move the long task to a seperate class. Implement it as a subject-observer pattern. Whenever the activity is created register and while closing unregister with the task class. Task class can use AsyncTask.
The trick is to show/dismiss the dialog within AsyncTask during onPreExecute/onPostExecute as usual, though in case of orientation-change create/show a new instance of the dialog in the activity and pass its reference to the task.
public class MainActivity extends Activity {
private Button mButton;
private MyTask mTask = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyTask task = (MyTask) getLastNonConfigurationInstance();
if(task != null){
mTask = task;
mTask.mContext = this;
mTask.mDialog = ProgressDialog.show(this, "", "", true);
}
mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
mTask = new MyTask(MainActivity.this);
mTask.execute();
}
});
}
#Override
public Object onRetainNonConfigurationInstance() {
String str = "null";
if(mTask != null){
str = mTask.toString();
mTask.mDialog.dismiss();
}
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
return mTask;
}
private class MyTask extends AsyncTask<Void, Void, Void>{
private ProgressDialog mDialog;
private MainActivity mContext;
public MyTask(MainActivity context){
super();
mContext = context;
}
protected void onPreExecute() {
mDialog = ProgressDialog.show(MainActivity.this, "", "", true);
}
protected void onPostExecute(Void result) {
mContext.mTask = null;
mDialog.dismiss();
}
#Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(5000);
return null;
}
}
}
I have done it like this:
package com.palewar;
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
public class ThreadActivity extends Activity {
static ProgressDialog dialog;
private Thread downloadThread;
final static Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
dialog.dismiss();
}
};
protected void onDestroy() {
super.onDestroy();
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
downloadThread = (Thread) getLastNonConfigurationInstance();
if (downloadThread != null && downloadThread.isAlive()) {
dialog = ProgressDialog.show(ThreadActivity.this, "",
"Signing in...", false);
}
dialog = ProgressDialog.show(ThreadActivity.this, "",
"Signing in ...", false);
downloadThread = new MyThread();
downloadThread.start();
// processThread();
}
// Save the thread
#Override
public Object onRetainNonConfigurationInstance() {
return downloadThread;
}
static public class MyThread extends Thread {
#Override
public void run() {
try {
// Simulate a slow network
try {
new Thread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
} finally {
}
}
}
}
You can also try and let me know it works for you or not
This is my proposed solution:
Move the AsyncTask or Thread to a retained Fragment, as explained here. I believe it is a good practice to move all network calls to fragments. If you are already using fragments, one of them could be made responsible for the calls. Otherwise, you can create a fragment just for doing the request, as the linked article proposes.
The fragment will use a listener interface to signal the task completion/failure. You don't have to worry for orientation changes there. The fragment will always have the correct link to the current activity and progress dialog can be safely resumed.
Make your progress dialog a member of your class. In fact you should do that for all dialogs. In the onPause method you should dismiss them, otherwise you will leak a window on the configuration change. The busy state should be kept by the fragment. When the fragment is attached to the activity, you can bring up the progress dialog again, if the call is still running. A void showProgressDialog() method can be added to the fragment-activity listener interface for this purpose.
If you create a background Service that does all the heavy lifting (tcp requests/response, unmarshalling), the View and Activity can be destroyed and re-created without leaking window or losing data. This allows the Android recommended behavior, which is to destroy an Activity on each configuration change (eg. for each orientation change).
It is a bit more complex, but it is the best way for invoking server request, data pre/post-processing, etc.
You may even use your Service to queue each request to a server, so it makes it easy and efficient to handle those things.
The dev guide has a full chapter on Services.
I have an implementation which allows the activity to be destroyed on a screen orientation change, but still destroys the dialog in the recreated activity successfully.
I use ...NonConfigurationInstance to attach the background task to the recreated activity.
The normal Android framework handles recreating the dialog itself, nothing is changed there.
I subclassed AsyncTask adding a field for the 'owning' activity, and a method to update this owner.
class MyBackgroundTask extends AsyncTask<...> {
MyBackgroundTask (Activity a, ...) {
super();
this.ownerActivity = a;
}
public void attach(Activity a) {
ownerActivity = a;
}
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
ownerActivity.dismissDialog(DIALOG_PROGRESS);
}
...
}
In my activity class I added a field backgroundTask referring to the 'owned' backgroundtask, and I update this field using onRetainNonConfigurationInstance and getLastNonConfigurationInstance.
class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
...
if (getLastNonConfigurationInstance() != null) {
backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
backgroundTask.attach(this);
}
}
void startBackgroundTask() {
backgroundTask = new MyBackgroundTask(this, ...);
showDialog(DIALOG_PROGRESS);
backgroundTask.execute(...);
}
public Object onRetainNonConfigurationInstance() {
if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
return backgroundTask;
return null;
}
...
}
Suggestions for further improvement:
Clear the backgroundTask reference in the activity after the task is finished to release any memory or other resources associated with it.
Clear the ownerActivity reference in the backgroundtask before the activity is destroyed in case it will not be recreated immediately.
Create a BackgroundTask interface and/or collection to allow different types of tasks to run from the same owning activity.
If you maintain two layouts, all UI thread should be terminated.
If you use AsynTask, then you can easily call .cancel() method inside onDestroy() method of current activity.
#Override
protected void onDestroy (){
removeDialog(DIALOG_LOGIN_ID); // remove loading dialog
if (loginTask != null){
if (loginTask.getStatus() != AsyncTask.Status.FINISHED)
loginTask.cancel(true); //cancel AsyncTask
}
super.onDestroy();
}
For AsyncTask, read more in "Cancelling a task" section at here.
Update:
Added condition to check status, as it can be only cancelled if it is in running state.
Also note that the AsyncTask can only be executed one time.
Tried to implement jfelectron's solution because it is a "rock-solid solution to these issues that conforms with the 'Android Way' of things" but it took some time to look up and put together all the elements mentioned. Ended up with this slightly different, and I think more elegant, solution posted here in it's entirety.
Uses an IntentService fired from an activity to perform the long running task on a separate thread. The service fires back sticky Broadcast Intents to the activity which update the dialog. The Activity uses showDialog(), onCreateDialog() and onPrepareDialog() to eliminate the need to have persistent data passed in the application object or the savedInstanceState bundle. This should work no matter how your application is interrupted.
Activity Class:
public class TesterActivity extends Activity {
private ProgressDialog mProgressDialog;
private static final int PROGRESS_DIALOG = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b = (Button) this.findViewById(R.id.test_button);
b.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
buttonClick();
}
});
}
private void buttonClick(){
clearPriorBroadcast();
showDialog(PROGRESS_DIALOG);
Intent svc = new Intent(this, MyService.class);
startService(svc);
}
protected Dialog onCreateDialog(int id) {
switch(id) {
case PROGRESS_DIALOG:
mProgressDialog = new ProgressDialog(TesterActivity.this);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setMax(MyService.MAX_COUNTER);
mProgressDialog.setMessage("Processing...");
return mProgressDialog;
default:
return null;
}
}
#Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch(id) {
case PROGRESS_DIALOG:
// setup a broadcast receiver to receive update events from the long running process
IntentFilter filter = new IntentFilter();
filter.addAction(MyService.BG_PROCESS_INTENT);
registerReceiver(new MyBroadcastReceiver(), filter);
break;
}
}
public class MyBroadcastReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
if (intent.hasExtra(MyService.KEY_COUNTER)){
int count = intent.getIntExtra(MyService.KEY_COUNTER, 0);
mProgressDialog.setProgress(count);
if (count >= MyService.MAX_COUNTER){
dismissDialog(PROGRESS_DIALOG);
}
}
}
}
/*
* Sticky broadcasts persist and any prior broadcast will trigger in the
* broadcast receiver as soon as it is registered.
* To clear any prior broadcast this code sends a blank broadcast to clear
* the last sticky broadcast.
* This broadcast has no extras it will be ignored in the broadcast receiver
* setup in onPrepareDialog()
*/
private void clearPriorBroadcast(){
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(MyService.BG_PROCESS_INTENT);
sendStickyBroadcast(broadcastIntent);
}}
IntentService Class:
public class MyService extends IntentService {
public static final String BG_PROCESS_INTENT = "com.mindspiker.Tester.MyService.TEST";
public static final String KEY_COUNTER = "counter";
public static final int MAX_COUNTER = 100;
public MyService() {
super("");
}
#Override
protected void onHandleIntent(Intent intent) {
for (int i = 0; i <= MAX_COUNTER; i++) {
Log.e("Service Example", " " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(BG_PROCESS_INTENT);
broadcastIntent.putExtra(KEY_COUNTER, i);
sendStickyBroadcast(broadcastIntent);
}
}}
Manifest file entries:
before application section:
uses-permission android:name="com.mindspiker.Tester.MyService.TEST"
uses-permission android:name="android.permission.BROADCAST_STICKY"
inside application section
service android:name=".MyService"
I faced the same situation. What I did was get only one instance for my progress dialog in the entire application.
First, I created a DialogSingleton class to get only one instance (Singleton pattern)
public class DialogSingleton
{
private static Dialog dialog;
private static final Object mLock = new Object();
private static DialogSingleton instance;
private DialogSingleton()
{
}
public static DialogSingleton GetInstance()
{
synchronized (mLock)
{
if(instance == null)
{
instance = new DialogSingleton();
}
return instance;
}
}
public void DialogShow(Context context, String title)
{
if(!((Activity)context).isFinishing())
{
dialog = new ProgressDialog(context, 2);
dialog.setCanceledOnTouchOutside(false);
dialog.setTitle(title);
dialog.show();
}
}
public void DialogDismiss(Context context)
{
if(!((Activity)context).isFinishing() && dialog.isShowing())
{
dialog.dismiss();
}
}
}
As I show in this class, I have the progress dialog as attribute. Every time I need to show a progress dialog, I get the unique instance and create a new ProgressDialog.
DialogSingleton.GetInstance().DialogShow(this, "My title here!");
When I am done with the background task, I call again the unique instance and dismiss its dialog.
DialogSingleton.GetInstance().DialogDismiss(this);
I save the background task status in my shared preferences. When I rotate the screen, I ask if I have a task running for this activity: (onCreate)
if(Boolean.parseBoolean(preference.GetValue(IS_TASK_NAME_EXECUTED_KEY, "boolean").toString()))
{
DialogSingleton.GetInstance().DialogShow(this, "Checking credentials!");
} // preference object gets the info from shared preferences (my own implementation to get and put data to shared preferences) and IS_TASK_NAME_EXECUTED_KEY is the key to save this flag (flag to know if this activity has a background task already running).
When I start running a background task:
preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean");
DialogSingleton.GetInstance().DialogShow(this, "My title here!");
When I finish running a background task:
preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean");
DialogSingleton.GetInstance().DialogDismiss(ActivityName.this);
I hope it helps.
This is a very old question that came up on the sidebar for some reason.
If the background task only needs to survive while the activity is in the foreground, the "new" solution is to host the background thread (or, preferably, AsyncTask) in a retained fragment, as described in this developer guide and numerous Q&As.
A retained fragment survives if the activity is destroyed for a configuration change, but not when the activity is destroyed in the background or back stack. Therefore, the background task should still be interrupted if isChangingConfigurations() is false in onPause().
I am a fresher in android and I tried this and it's worked.
public class loadTotalMemberByBranch extends AsyncTask<Void, Void,Void> {
ProgressDialog progressDialog = new ProgressDialog(Login.this);
int ranSucess=0;
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
progressDialog.setTitle("");
progressDialog.isIndeterminate();
progressDialog.setCancelable(false);
progressDialog.show();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
}
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
return null;
}
#Override
protected void onPostExecute(Void result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
progressDialog.dismiss();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
}
}
I've tried EVERYTHING. Spent days experimenting. I didn't want to block the activity from rotating. My scenario was:
A progress dialog showing dynamic information to the user. E.g.: "Connecting to server...", "Downloading data...", etc.
A thread doing the heavy stuff and updating the dialog
Updating the UI with the results at the end.
The problem was, when rotating the screen, every solution on the book failed. Even with the AsyncTask class, which is the correct Android way of dealing with this situations. When rotating the screen, the current Context that the starting thread is working with, is gone, and that messes up with the dialog that is showing. The problem was always the Dialog, no matter how many tricks I added to the code (passing new contexts to running threads, retaining thread states through rotations, etc...). The code complexity at the end was always huge and there was always something that could go wrong.
The only solution that worked for me was the Activity/Dialog trick. It's simple and genius and it's all rotation proof:
Instead of creating a Dialog and ask to show it, create an Activity that has been set in the manifest with android:theme="#android:style/Theme.Dialog". So, it just looks like a dialog.
Replace showDialog(DIALOG_ID) with startActivityForResult(yourActivityDialog, yourCode);
Use onActivityResult in the calling Activity to get the results from the executing thread (even the errors) and update the UI.
On your 'ActivityDialog', use threads or AsyncTask to execute long tasks and onRetainNonConfigurationInstance to save "dialog" state when rotating the screen.
This is fast and works fine. I still use dialogs for other tasks and the AsyncTask for something that doesn't require a constant dialog on screen. But with this scenario, I always go for the Activity/Dialog pattern.
And, I didn't try it, but it's even possible to block that Activity/Dialog from rotating, when the thread is running, speeding things up, while allowing the calling Activity to rotate.
These days there is a much more distinct way to handle these types of issues. The typical approach is:
1. Ensure your data is properly seperated from the UI:
Anything that is a background process should be in a retained Fragment (set this with Fragment.setRetainInstance(). This becomes your 'persistent data storage' where anything data based that you would like retained is kept. After the orientation change event, this Fragment will still be accessible in its original state through a FragmentManager.findFragmentByTag() call (when you create it you should give it a tag not an ID as it is not attached to a View).
See the Handling Runtime Changes developed guide for information about doing this correctly and why it is the best option.
2. Ensure you are interfacing correctly and safely between the background processs and your UI:
You must reverse your linking process. At the moment your background process attaches itself to a View - instead your View should be attaching itself to the background process. It makes more sense right? The View's action is dependent on the background process, whereas the background process is not dependent on the View.This means changing the link to a standard Listener interface. Say your process (whatever class it is - whether it is an AsyncTask, Runnable or whatever) defines a OnProcessFinishedListener, when the process is done it should call that listener if it exists.
This answer is a nice concise description of how to do custom listeners.
3. Link your UI into the data process whenever the UI is created (including orientation changes):
Now you must worry about interfacing the background task with whatever your current View structure is. If you are handling your orientation changes properly (not the configChanges hack people always recommend), then your Dialog will be recreated by the system. This is important, it means that on the orientation change, all your Dialog's lifecycle methods are recalled. So in any of these methods (onCreateDialog is usually a good place), you could do a call like the following:
DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG");
if (f != null) {
f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() {
public void onProcessFinished() {
dismiss();
}
});
}
See the Fragment lifecycle for deciding where setting the listener best fits in your individual implementation.
This is a general approach to providing a robust and complete solution to the generic problem asked in this question. There is probably a few minor pieces missing in this answer depending on your individual scenario, but this is generally the most correct approach for properly handling orientation change events.
i have found and easier solution to handle threads when orientation change. You can just keep an static reference to your activity/fragment and verify if its null before acting on the ui. I suggest using a try catch too:
public class DashListFragment extends Fragment {
private static DashListFragment ACTIVE_INSTANCE;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ACTIVE_INSTANCE = this;
new Handler().postDelayed(new Runnable() {
public void run() {
try {
if (ACTIVE_INSTANCE != null) {
setAdapter(); // this method do something on ui or use context
}
}
catch (Exception e) {}
}
}, 1500l);
}
#Override
public void onDestroy() {
super.onDestroy();
ACTIVE_INSTANCE = null;
}
}
If you're struggling with detecting orientation change events of a dialog INDEPENDENT OF AN ACTIVITY REFERENCE, this method works excitingly well. I use this because I have my own dialog class that can be shown in multiple different Activities so I don't always know which Activity it's being shown in. With this method you don't need to change the AndroidManifest, worry about Activity references, and you don't need a custom dialog (as I have). You do need, however, a custom content view so you can detect the orientation changes using that particular view. Here's my example:
Setup
public class MyContentView extends View{
public MyContentView(Context context){
super(context);
}
#Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
//DO SOMETHING HERE!! :D
}
}
Implementation 1 - Dialog
Dialog dialog = new Dialog(context);
//set up dialog
dialog.setContentView(new MyContentView(context));
dialog.show();
Implementation 2 - AlertDialog.Builder
AlertDialog.Builder builder = new AlertDialog.Builder(context);
//set up dialog builder
builder.setView(new MyContentView(context)); //Can use this method
builder.setCustomTitle(new MycontentView(context)); // or this method
builder.build().show();
Implementation 3 - ProgressDialog / AlertDialog
ProgressDialog progress = new ProgressDialog(context);
//set up progress dialog
progress.setView(new MyContentView(context)); //Can use this method
progress.setCustomTitle(new MyContentView(context)); // or this method
progress.show();
This is my solution when I faced it:
ProgressDialog is not a Fragment child, so my custom class "ProgressDialogFragment" can extend DialogFragment instead in order to keep the dialog shown for configuration changes.
import androidx.annotation.NonNull;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
/**
* Usage:
* To display the dialog:
* >>> ProgressDialogFragment.showProgressDialogFragment(
* getSupportFragmentManager(),
* "fragment_tag",
* "my dialog title",
* "my dialog message");
*
* To hide the dialog
* >>> ProgressDialogFragment.hideProgressDialogFragment();
*/
public class ProgressDialogFragment extends DialogFragment {
private static String sTitle, sMessage;
private static ProgressDialogFragment sProgressDialogFragment;
public ProgressDialogFragment() {
}
private ProgressDialogFragment(String title, String message) {
sTitle = title;
sMessage = message;
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return ProgressDialog.show(getActivity(), sTitle, sMessage);
}
public static void showProgressDialogFragment(FragmentManager fragmentManager, String fragmentTag, String title, String message) {
if (sProgressDialogFragment == null) {
sProgressDialogFragment = new ProgressDialogFragment(title, message);
sProgressDialogFragment.show(fragmentManager, fragmentTag);
} else { // case of config change (device rotation)
sProgressDialogFragment = (ProgressDialogFragment) fragmentManager.findFragmentByTag(fragmentTag); // sProgressDialogFragment will try to survive its state on configuration as much as it can, but when calling .dismiss() it returns NPE, so we have to reset it on each config change
sTitle = title;
sMessage = message;
}
}
public static void hideProgressDialogFragment() {
if (sProgressDialogFragment != null) {
sProgressDialogFragment.dismiss();
}
}
}
The challenge was to retain the dialog title & message while screen
rotation as they reset to the default empty string, although the dialog still shown
There are 2 approaches to solve this:
First approach:
Make the activity that utilizes the dialog to retain state during config change in manifest file:
android:configChanges="orientation|screenSize|keyboardHidden"
This approach is not preferred by Google.
Second approach:
on the activity's onCreate() method, you need to retain your DialogFragment by rebuilding the ProgressDialogFragment again with the title & message as follows if the savedInstanceState is not null:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_deal);
if (savedInstanceState != null) {
ProgressDialogFragment saveProgressDialog = (ProgressDialogFragment) getSupportFragmentManager()
.findFragmentByTag("fragment_tag");
if (saveProgressDialog != null) {
showProgressDialogFragment(getSupportFragmentManager(), "fragment_tag", "my dialog title", "my dialog message");
}
}
}
Seems far too 'quick and dirty' to be true so please point out the flaws but what I found worked was...
Within the onPostExecute method of my AsyncTask, I simply wrapped the '.dismiss' for the progress dialog in a try/catch block (with an empty catch) and then simply ignored the exception that was raised. Seems wrong to do but appears there are no ill effects (at least for what I am doing subsequently which is to start another activity passing in the result of my long running query as an Extra)
The simplest and most flexible solution is to use an AsyncTask with a static reference to ProgressBar. This provides an encapsulated and thus reusable solution to orientation change problems. This solution has served me well for varying asyncronous tasks including internet downloads, communicating with Services, and filesystem scans. The solution has been well tested on multiple android versions and phone models. A complete demo can be found here with specific interest in DownloadFile.java
I present the following as a concept example
public class SimpleAsync extends AsyncTask<String, Integer, String> {
private static ProgressDialog mProgressDialog = null;
private final Context mContext;
public SimpleAsync(Context context) {
mContext = context;
if ( mProgressDialog != null ) {
onPreExecute();
}
}
#Override
protected void onPreExecute() {
mProgressDialog = new ProgressDialog( mContext );
mProgressDialog.show();
}
#Override
protected void onPostExecute(String result) {
if ( mProgressDialog != null ) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
}
#Override
protected void onProgressUpdate(Integer... progress) {
mProgressDialog.setProgress( progress[0] );
}
#Override
protected String doInBackground(String... sUrl) {
// Do some work here
publishProgress(1);
return null;
}
public void dismiss() {
if ( mProgressDialog != null ) {
mProgressDialog.dismiss();
}
}
}
Usage in an Android Activity is simple
public class MainActivity extends Activity {
DemoServiceClient mClient = null;
DownloadFile mDownloadFile = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView( R.layout.main );
mDownloadFile = new DownloadFile( this );
Button downloadButton = (Button) findViewById( R.id.download_file_button );
downloadButton.setOnClickListener( new View.OnClickListener() {
#Override
public void onClick(View view) {
mDownloadFile.execute( "http://www.textfiles.com/food/bakebred.txt");
}
});
}
#Override
public void onPause() {
super.onPause();
mDownloadFile.dismiss();
}
}
When you change orientations , Android kill that activity and created new activity .
I suggest to use retrofit with Rx java . which handle crashes automatically .
Use these method when retrofit call.
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

How to set/change Android Studio 'Fullscreen Activity' background?

So I created a new activity using the 'Fullscreen Activity' template in Android Studio. I now want to change the background which is set to android:background="#0099cc" by default in its generated XML file. However, I've tried the 'solution' on one of the more popular, similar questions but got a error.
How to set background color of an Activity to white programmatically?
The error I get is:
Caused by: android.content.res.Resources$NotFoundException: Resource ID #0xffffffff
at android.content.res.Resources.getValue(Resources.java:2060)
at android.content.res.Resources.getDrawable(Resources.java:1625)
at android.view.View.setBackgroundResource(View.java:16251)
which happens at the root.setBackgroundResource(Color.WHITE); line. Below is the entirety of the java code with the only two things added to the Activity template being the onStart method and a setActivityBackgroundColor() method.
package org.test.app;
import org.test.app.util.SystemUiHider;
import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import org.test.app.R;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*
* #see SystemUiHider
*/
public class FullscreenActivityTest extends Activity {
/**
* Whether or not the system UI should be auto-hidden after
* {#link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
*/
private static final boolean AUTO_HIDE = true;
/**
* If {#link #AUTO_HIDE} is set, the number of milliseconds to wait after
* user interaction before hiding the system UI.
*/
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/**
* If set, will toggle the system UI visibility upon interaction. Otherwise,
* will show the system UI visibility upon interaction.
*/
private static final boolean TOGGLE_ON_CLICK = true;
/**
* The flags to pass to {#link SystemUiHider#getInstance}.
*/
private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
/**
* The instance of the {#link SystemUiHider} for this activity.
*/
private SystemUiHider mSystemUiHider;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen_activity_test);
final View controlsView = findViewById(R.id.fullscreen_content_controls);
final View contentView = findViewById(R.id.fullscreen_content);
// Set up an instance of SystemUiHider to control the system UI for
// this activity.
mSystemUiHider = SystemUiHider.getInstance(this, contentView, HIDER_FLAGS);
mSystemUiHider.setup();
mSystemUiHider
.setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
// Cached values.
int mControlsHeight;
int mShortAnimTime;
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void onVisibilityChange(boolean visible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
// If the ViewPropertyAnimator API is available
// (Honeycomb MR2 and later), use it to animate the
// in-layout UI controls at the bottom of the
// screen.
if (mControlsHeight == 0) {
mControlsHeight = controlsView.getHeight();
}
if (mShortAnimTime == 0) {
mShortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
controlsView.animate()
.translationY(visible ? 0 : mControlsHeight)
.setDuration(mShortAnimTime);
} else {
// If the ViewPropertyAnimator APIs aren't
// available, simply show or hide the in-layout UI
// controls.
controlsView.setVisibility(visible ? View.VISIBLE : View.GONE);
}
if (visible && AUTO_HIDE) {
// Schedule a hide().
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
}
});
// Set up the user interaction to manually show or hide the system UI.
contentView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (TOGGLE_ON_CLICK) {
mSystemUiHider.toggle();
} else {
mSystemUiHider.show();
}
}
});
// Upon interacting with UI controls, delay any scheduled hide()
// operations to prevent the jarring behavior of controls going away
// while interacting with the UI.
findViewById(R.id.dummy_button).setOnTouchListener(mDelayHideTouchListener);
}
protected void onStart() {
super.onStart();
setActivityBackgroundColor();
}
public void setActivityBackgroundColor() {
setContentView(R.layout.activity_fullscreen_activity_test);
View root = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
root.setBackgroundResource(Color.WHITE);
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
delayedHide(100);
}
/**
* Touch listener to use for in-layout UI controls to delay hiding the
* system UI. This is to prevent the jarring behavior of controls going away
* while interacting with activity UI.
*/
View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (AUTO_HIDE) {
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
return false;
}
};
Handler mHideHandler = new Handler();
Runnable mHideRunnable = new Runnable() {
#Override
public void run() {
mSystemUiHider.hide();
}
};
/**
* Schedules a call to hide() in [delay] milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide(int delayMillis) {
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis);
}
}
setBackgroundResource takes a resource, not a color (even though both are integers). You probably want to use View.setBackgroundColor if you want to set it to a color in code.

Flip Images in a viewpager

I have a viewpager for a fullscreen image slider with horizontal swipe. What I am trying to do is put an image behind every image on the slider. Meaning, a user can click on a 'flip' button to reveal another image.
Below is my code however I have two issues:
The first time I click the flip button, it flips the image but only
to reveal the same image. However, when I click it the second time
it works :).
The flip button only works on the first image. When I swipe
right or left the flip button doesn't work anymore. Any help?
Note: I am using a fullscreenActivity theme which hides the UI control until the user clicks on the image.
FullscreenActivity.java
package me.zamaaan.wallpaper;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import me.zamaaan.wallpaper.util.SystemUiHider;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import static me.zamaaan.wallpaper.HeavyLifter.FAIL;
import static me.zamaaan.wallpaper.HeavyLifter.SUCCESS;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import java.util.ArrayList;
import java.util.List;
/** A helper class that will do the heavy work of decoding images and actually setting the wallpaper */
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*
* #see SystemUiHider
*/
public class FullscreenActivity extends Activity implements OnClickListener, AnimationListener{
/**
* Whether or not the system UI should be auto-hidden after
* {#link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
*/
private static final boolean AUTO_HIDE = true;
private HeavyLifter chuckNorris;
private Animation animation1;
private Animation animation2;
private boolean isBackOfCardShowing = true;
/*
an array with the ids of the images for the viewpager
*/
public Integer[] mImageIds = {
R.drawable.background1, R.drawable.background2,
R.drawable.background3, R.drawable.background4,
R.drawable.background1, R.drawable.background2,
R.drawable.background1, R.drawable.background2,
R.drawable.background3, R.drawable.background4,
R.drawable.background1, R.drawable.background2,
R.drawable.background3
};
/*
an array with the ids of the images for the back flip of each image in the viewpager
*/
public Integer[] mBackIds = {
R.drawable.splash, R.drawable.splash,
R.drawable.splash, R.drawable.splash,
R.drawable.splash, R.drawable.splash,
R.drawable.splash, R.drawable.splash,
R.drawable.splash, R.drawable.splash,
R.drawable.splash, R.drawable.splash,
R.drawable.splash
};
/**
* If {#link #AUTO_HIDE} is set, the number of milliseconds to wait after
* user interaction before hiding the system UI.
*/
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/**
* If set, will toggle the system UI visibility upon interaction. Otherwise,
* will show the system UI visibility upon interaction.
*/
private static final boolean TOGGLE_ON_CLICK = true;
/**
* The flags to pass to {#link SystemUiHider#getInstance}.
*/
private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
/**
* The instance of the {#link SystemUiHider} for this activity.
*/
private SystemUiHider mSystemUiHider;
/**
* The pager widget, which handles animation and allows swiping horizontally to access previous
* and next wizard steps.
*/
private ViewPager mPager;
/**
* The pager adapter, which provides the pages to the view pager widget.
*/
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
animation1 = AnimationUtils.loadAnimation(this, R.anim.to_middle);
animation1.setAnimationListener(this);
animation2 = AnimationUtils.loadAnimation(this, R.anim.from_middle);
animation2.setAnimationListener(this);
findViewById(R.id.flip).setOnClickListener(this);
// Loop through the ids to create a list of full screen image views
ImageAdapter imageAdapter = new ImageAdapter(this);
List<ImageView> images = new ArrayList<ImageView>();
for (int i = 0; i < imageAdapter.getCount(); i++) {
ImageView imageView = new ImageView(this);
imageView.setId(i);
imageView.setImageResource(imageAdapter.mThumbIds[i]);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
images.add(imageView);
}
final View controlsView = findViewById(R.id.fullscreen_content_controls);
final View contentView = findViewById(R.id.view_pager);
// Set up an instance of SystemUiHider to control the system UI for
// this activity.
mSystemUiHider = SystemUiHider.getInstance(this, contentView, HIDER_FLAGS);
mSystemUiHider.setup();
mSystemUiHider.setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
// Cached values.
int mControlsHeight;
int mShortAnimTime;
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void onVisibilityChange(boolean visible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
// If the ViewPropertyAnimator API is available
// (Honeycomb MR2 and later), use it to animate the
// in-layout UI controls at the bottom of the
// screen.
if (mControlsHeight == 0) {
mControlsHeight = controlsView.getHeight();
}
if (mShortAnimTime == 0) {
mShortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
controlsView
.animate()
.translationY(visible ? 0 : mControlsHeight)
.setDuration(mShortAnimTime);
} else {
// If the ViewPropertyAnimator APIs aren't
// available, simply show or hide the in-layout UI
// controls.
controlsView.setVisibility(visible ? View.VISIBLE
: View.GONE);
}
if (visible && AUTO_HIDE) {
// Schedule a hide().
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
}
});
// Set up the user interaction to manually show or hide the system UI.
contentView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (TOGGLE_ON_CLICK) {
mSystemUiHider.toggle();
} else {
mSystemUiHider.show();
}
}
});
// Upon interacting with UI controls, delay any scheduled hide()
// operations to prevent the jarring behavior of controls going away
// while interacting with the UI.
findViewById(R.id.btnSetAsWallpaper).setOnTouchListener(
mDelayHideTouchListener);
findViewById(R.id.btnSaveWallpaper).setOnTouchListener(
mDelayHideTouchListener);
// Finally create the adapter
ImagePagerAdapter imagePagerAdapter = new ImagePagerAdapter(images);
ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
viewPager.setAdapter(imagePagerAdapter);
// Set the ViewPager to point to the selected image from the previous activity
// Selected image id
int position = getIntent().getExtras().getInt("id");
viewPager.setCurrentItem(position);
//set title of Image
this.setTitle(imageAdapter.mThumbTitles[position]);
// Load are heavy lifter (goes and does work on another thread), to get a response after the lifters thread
// has finished we pass in a Handler that will be notified when it completes
chuckNorris = new HeavyLifter(this, chuckFinishedHandler);
}
#Override
public void onClick(View v) {
v.setEnabled(false);
((ImageView)findViewById(getIntent().getExtras().getInt("id"))).clearAnimation();
((ImageView)findViewById(getIntent().getExtras().getInt("id"))).setAnimation(animation1);
((ImageView)findViewById(getIntent().getExtras().getInt("id"))).startAnimation(animation1);
}
#Override
public void onAnimationEnd(Animation animation) {
int i = getIntent().getExtras().getInt("id");
if (animation==animation1) {
if (isBackOfCardShowing) {
((ImageView)findViewById(i)).setImageResource(mImageIds[i]);
} else {
((ImageView)findViewById(i)).setImageResource(mBackIds[i]);
}
((ImageView)findViewById(getIntent().getExtras().getInt("id"))).clearAnimation();
((ImageView)findViewById(getIntent().getExtras().getInt("id"))).setAnimation(animation2);
((ImageView)findViewById(getIntent().getExtras().getInt("id"))).startAnimation(animation2);
} else {
isBackOfCardShowing=!isBackOfCardShowing;
findViewById(R.id.flip).setEnabled(true);
}
}
#Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
delayedHide(100);
}
/**
* Touch listener to use for in-layout UI controls to delay hiding the
* system UI. This is to prevent the jarring behavior of controls going away
* while interacting with activity UI.
*/
View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (AUTO_HIDE) {
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
return false;
}
};
Handler mHideHandler = new Handler();
Runnable mHideRunnable = new Runnable() {
#Override
public void run() {
mSystemUiHider.hide();
}
};
/**
* Schedules a call to hide() in [delay] milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide(int delayMillis) {
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis);
}
/**
* Called from XML when the save wallpaper button is pressed
* Thie retrieves the id of the current image from our list
* It then asks chuck to save it as a wallpaper!
* The chuckHandler will be called when this operation is complete
* #param v
*/
public void saveWallpaper(View v) {
// get intent data
Intent i = getIntent();
// Selected image id
int position = i.getExtras().getInt("id");
ImageAdapter imageAdapter = new ImageAdapter(this);
// Show a toast message on successful save
if(chuckNorris.saveResourceAsWallpaper(imageAdapter.mThumbIds[position])){
Toast.makeText(FullscreenActivity.this, "Image Saved to ZamaaanWallpaper folder", Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(FullscreenActivity.this, "Image couldn't be saved, try again", Toast.LENGTH_SHORT).show();
}
}
/**
* Called from XML when the set wallpaper button is pressed
* Thie retrieves the id of the current image from our list
* It then asks chuck to set it as a wallpaper!
* The chuckHandler will be called when this operation is complete
* #param v
*/
public void setAsWallpaper(View v) {
// get intent data
Intent i = getIntent();
// Selected image id
int position = i.getExtras().getInt("id");
ImageAdapter imageAdapter = new ImageAdapter(this);
chuckNorris.setResourceAsWallpaper(imageAdapter.mThumbIds[position]);
}
/**
* This is the handler that is notified when are HeavyLifter is finished doing an operation
*/
private Handler chuckFinishedHandler = new Handler(){
#Override
public void handleMessage(Message msg) {
switch(msg.what){
case SUCCESS:
Toast.makeText(FullscreenActivity.this, "Wallpaper set", Toast.LENGTH_SHORT).show();
break;
case FAIL:
Toast.makeText(FullscreenActivity.this, "Wallper NOT set, try again", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
};
}
I fixed my issues. I simply had to :
Change the boolean isBackOfCardShowing to an array of boolean for each page and intialize it to false.
Use viewpager.getCurrentItem() to get the position of the current page instead of what I was doing.

Search Dialog won't appear with TabHost implementation

I have created my tabs using the following example code and in the comments it was noted that many people could no longer call upon the search dialog and I have been able to no longer have my program force close when you click on the hardware search button but when you click on the button it doesn't pop up anything anymore.
Would anyone know how to fix this?
Multiple Android Activities with TabActivity
import java.util.ArrayList;
import android.app.Activity;
import android.app.ActivityGroup;
import android.app.LocalActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Window;
/**
* The purpose of this Activity is to manage the activities in a tab.
* Note: Child Activities can handle Key Presses before they are seen here.
* #author Eric Harlow
*/
public class TabGroupActivity extends ActivityGroup {
private ArrayList<String> mIdList;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mIdList == null) mIdList = new ArrayList<String>();
}
/**
* This is called when a child activity of this one calls its finish method.
* This implementation calls {#link LocalActivityManager#destroyActivity} on the child activity
* and starts the previous activity.
* If the last child activity just called finish(),this activity (the parent),
* calls finish to finish the entire group.
*/
#Override
public void finishFromChild(Activity child) {
LocalActivityManager manager = getLocalActivityManager();
int index = mIdList.size()-1;
if (index < 1) {
finish();
return;
}
manager.destroyActivity(mIdList.get(index), true);
mIdList.remove(index);
index--;
String lastId = mIdList.get(index);
Intent lastIntent = manager.getActivity(lastId).getIntent();
Window newWindow = manager.startActivity(lastId, lastIntent);
setContentView(newWindow.getDecorView());
}
/**
* Starts an Activity as a child Activity to this.
* #param Id Unique identifier of the activity to be started.
* #param intent The Intent describing the activity to be started.
* #throws android.content.ActivityNotFoundException.
*/
public void startChildActivity(String Id, Intent intent) {
Window window = getLocalActivityManager().startActivity(Id,intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
if (window != null) {
mIdList.add(Id);
setContentView(window.getDecorView());
}
}
/**
* The primary purpose is to prevent systems before android.os.Build.VERSION_CODES.ECLAIR
* from calling their default KeyEvent.KEYCODE_BACK during onKeyDown.
*/
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
//preventing default implementation previous to android.os.Build.VERSION_CODES.ECLAIR
return true;
}
return super.onKeyDown(keyCode, event);
}
/**
* Overrides the default implementation for KeyEvent.KEYCODE_BACK
* so that all systems call onBackPressed().
*/
#Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed();
return true;
}
return super.onKeyUp(keyCode, event);
}
/**
* If a Child Activity handles KeyEvent.KEYCODE_BACK.
* Simply override and add this method.
*/
#Override
public void onBackPressed () {
int length = mIdList.size();
if ( length > 1) {
Activity current = getLocalActivityManager().getActivity(mIdList.get(length-1));
current.finish();
}
}
}
#Override
public void onBackPressed() {
TabGroupActivity parentActivity = (TabGroupActivity)getParent();
parentActivity.onBackPressed();
}
The following section explains how I used this ActivityGroup. The first thing I did was subclass this ActivityGroup like this.
import android.content.Intent;
import android.os.Bundle;
public class TabGroup1Activity extends TabGroupActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startChildActivity("OptionsActivity", new Intent(this,OptionsActivity.class));
}
}
I added this TabGroup1Activity into my TabActivity like this.
TabHost tabHost = getTabHost();
tabHost.addTab(tabHost.newTabSpec("tab1")
.setIndicator("MESSAGES")
.setContent(new Intent(this, TabGroup1Activity.class)));
Then any Activity that you want to start in the ActivityGroup can be done in a way similar to this.
Intent frequentMessages = new Intent(getParent(), FrequentMessageActivity.class);
TabGroupActivity parentActivity = (TabGroupActivity)getParent();
parentActivity.startChildActivity("FrequentMessageActivity", frequentMessages);
I have made all my activities searchable in the Android Manifest and I followed the tutorial on implementing search.
Creating a Search Interface
Prior to modifying my code to work with the TabActivity the search was working as intended but something in the code has broken the search functionality.
I have found a solution which I am working on deciphering but I was hoping that someone with more know how would help in implementing it based on the example given in the tutorial example posted in the question.
Here is the solution that I found but haven't applied
Nested TabHosts

Drawing to a canvas using a custom method?

I'm coding for an android 2-D game and I'm having some trouble.
Currently, in my package, I have a thread which draws on an instance of a canvas. I'm drawing Drawable bitmaps from resources. What I would like to do is keep the thread handing the drawing of the background image. But have instances of custom objects able draw to that same canvas using drawable objects. This seems possible logically, but I can't get it to work. Any time I attempt to retrieve the resources in my custom class the app crashes on start-up.
Here were a couple of my efforts: (please don't laugh if i've done something stupid, i'm trying.)
public class Worker{
//Get drawables
// Resources res = getResources();
// Drawable man1 = res.getDrawable(R.drawable.workertest);
// Context mContext;
// Resources res = mContext.getResources();
// Drawable man1 = mContext.getResources().getDrawable(R.drawable.workertest);
// Drawable man2 = mContext.getResources().getDrawable(R.drawable.workertest1);
// Drawable man1 = res.getDrawable(R.drawable.workertest);
// Drawable man2 = res.getDrawable(R.drawable.workertest1);
}
As you can see I've tried a couple different methods, and I've also tried adding (extends activity) to my class, but I can't figure this out.
This code doesn't address actually drawing to the canvas, I have yet to cross that bridge
Edit: Here is the Activity. As you can see it calls a thread in lunarview. That thread instantiates the object that needs to retrieve the Drawable. how in gods name do i get the application context to the object? or is there another way to do it? And yes, this is the code from the android sample codes.
public class LunarLander extends Activity {
private static final int MENU_EASY = 1;
private static final int MENU_HARD = 2;
private static final int MENU_MEDIUM = 3;
private static final int MENU_PAUSE = 4;
private static final int MENU_RESUME = 5;
private static final int MENU_START = 6;
private static final int MENU_STOP = 7;
/** A handle to the thread that's actually running the animation. */
public static LunarThread mLunarThread;
/** A handle to the View in which the game is running. */
public LunarView mLunarView;
/**
* Invoked during init to give the Activity a chance to set up its Menu.
*
* #param menu the Menu to which entries may be added
* #return true
*/
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_START, 0, R.string.menu_start);
menu.add(0, MENU_STOP, 0, R.string.menu_stop);
menu.add(0, MENU_PAUSE, 0, R.string.menu_pause);
menu.add(0, MENU_RESUME, 0, R.string.menu_resume);
menu.add(0, MENU_EASY, 0, R.string.menu_easy);
menu.add(0, MENU_MEDIUM, 0, R.string.menu_medium);
menu.add(0, MENU_HARD, 0, R.string.menu_hard);
return true;
}
/**
* Invoked when the user selects an item from the Menu.
*
* #param item the Menu entry which was selected
* #return true if the Menu item was legit (and we consumed it), false
* otherwise
*/
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_START:
mLunarThread.doStart();
return true;
case MENU_STOP:
mLunarThread.setState(LunarThread.STATE_LOSE,
getText(R.string.message_stopped));
return true;
case MENU_PAUSE:
mLunarThread.pause();
return true;
case MENU_RESUME:
mLunarThread.unpause();
return true;
case MENU_EASY:
mLunarThread.setDifficulty(LunarThread.DIFFICULTY_EASY);
return true;
case MENU_MEDIUM:
mLunarThread.setDifficulty(LunarThread.DIFFICULTY_MEDIUM);
return true;
case MENU_HARD:
mLunarThread.setDifficulty(LunarThread.DIFFICULTY_HARD);
return true;
}
return false;
}
/**
* Invoked when the Activity is created.
*
* #param savedInstanceState a Bundle containing state saved from a previous
* execution, or null if this is a new execution
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// tell system to use the layout defined in our XML file
setContentView(R.layout.lunar_layout);
// get handles to the LunarView from XML, and its LunarThread
mLunarView = (LunarView) findViewById(R.id.lunar);
mLunarThread = mLunarView.getThread();
// give the LunarView a handle to the TextView used for messages
mLunarView.setTextView((TextView) findViewById(R.id.text));
if (savedInstanceState == null) {
// we were just launched: set up a new game
mLunarThread.setState(LunarThread.STATE_READY);
Log.w(this.getClass().getName(), "SIS is null");
} else {
// we are being restored: resume a previous game
mLunarThread.restoreState(savedInstanceState);
Log.w(this.getClass().getName(), "SIS is nonnull");
}
}
/**
* Invoked when the Activity loses user focus.
*/
#Override
protected void onPause() {
super.onPause();
mLunarView.getThread().pause(); // pause game when Activity pauses
}
/**
* Notification that something is about to happen, to give the Activity a
* chance to save state.
*
* #param outState a Bundle into which this Activity should save its state
*/
#Override
protected void onSaveInstanceState(Bundle outState) {
// just have the View's thread save its state into our Bundle
super.onSaveInstanceState(outState);
mLunarThread.saveState(outState);
Log.w(this.getClass().getName(), "SIS called");
}
public boolean onTouchEvent(MotionEvent event){
int clickX = Math.round(event.getX()/10)*10;
int clickY = Math.round(event.getY()/10)*10;
LunarView.xCo = clickX;
LunarView.yCo = clickY;
return true;
}
As Greg suggested, the problem is in getting the right Context to retrieve the resources.
An easy way to solve this is to pass the main Activity as an argument in the Worker class constructor, and save it as a private Context field. Something like:
public class Worker{
private Context mContext;
public Worker (Context context) {
mContext = context;
Resources res = mContext.getResources();
// all the rest of your code
}
}
You will use it in your main activity as:
Worker worker = new Worker(this);

Categories