I think I know why this happens.
When rotating the screen the Activity gets destroyed and recreated and the runOnUiThread() method is referencing the old(destroyed) Activity which isn't valid anymore and so android throws a runtimeexception.
To demonstrate this I have this code:
#ContentView(R.layout.activity_start)
public class StartActivity extends RoboSherlockFragmentActivity {
#Inject
private EventManager eventManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Fragment newFragment = new WelcomeFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, newFragment).commit();
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000);
//Do some short initialization here like read shared prefs
//and then decide for example whether to login or skip the login
//If the rotation happens while sleeping the app will certainly crash
runOnUiThread(new Runnable() {
#Override
public void run() {
addFragmentToStack();
}
});
} catch (Exception e) {
// TODO: handle exception
}
}
}).start();
}
}
void addFragmentToStack() {
Fragment newFragment = new LoginOrRegisterFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
}
Do I have to use an asynctask for some easy task like that? If so how I handle the rotation then? Because the callback reference would be faulty either.
try this
new Handler(getMainLooper()).postDelayed(new Runnable(){
if(!isFinishing()){
addFragmentToStack();
}
},200);
instead of your thread code
isFinishing() is called when the activity is in the process of being destoryed
It seems that all you're trying to do is to make the task executed after 200 ms have passed.
there is no need to open a new thread for that, a handler will do
2ndly if you want to ensure that the code will be executed on the main thread
you create the handler calling for the main looper
Handler(getMainLooper()) and it will make this handler execute its task on the main thread
AsyncTask will generate the same crash. You can Simply override onPause() and find a way to notify the thread that Activity is about to be destroyed. Like checking for null value of a variable.
public void run() {
if(someVariable != null)
addFragmentToStack();
}
Or you can use a callback fragment (without view) to keep track of configuration change. This article is a great way to start. This method I prefer because it is a very clean approach.
I had a similar problem and solved it by using a retain fragment where I store a reference to the Activity on every OnCreate e.g. retainFragment.activity = this; . In runOnUiThread I check if the retained reference is the current Activity e.g. if (retainFragment.activity == MainActivity.this)..
Related
I have a simple Fragment with this in the onViewCreated method:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (account != null) {
try {
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() ->
{
String decryptedCode;
try {
decryptedCode = (vaultService).getDecryptedPassword(account).trim();
in_password.setText(decryptedCode);
} catch (Exception e) {
Helper.showMessage(e.toString());
} finally {
in_password_layout.setHelperText("");
}
}, 1);
} catch (Exception e) {
Helper.showMessage(e.toString());
}
}
}
and calling the Fagment i have:
protected void openFragment(BaseFragment fragment) {
fragment.setCaller(this);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.activity_slide_in_right, R.anim.activity_slide_out_left, R.anim.activity_slide_out_right, R.anim.activity_slide_in_left);
fragmentTransaction.replace(R.id.content_frame, fragment);
fragmentTransaction.addToBackStack(fragment.getClass().getName());
fragmentTransaction.commit();
}
The problem that i am facing is that if i remove the Handler call from the onViewCreated method, the transition occurs perfectly. If i put the Handler, like so, it kills the animation and just shows the fragment without any animation.
If i use the handler.postInFrontOfQueue the animation works, but takes a while before coming in. Which means it processes the Handler first and only then executes the animation and transits to the Fragment.
Do you have any idea how can i prevent this? I already tried in an independent Thread and it does not work.
Thanks.
I just found a solution. I have to put the amount of time it takes for the animation to occur in the 2nd parameter of the postDelayed method.
In my case:
getResources().getInteger(android.R.integer.config_mediumAnimTime)
So it only triggers the handler after the animation finishes.
Not exactly what i wanted but it works. I would rather have it both process in parallel so that when the animation finishes the Handler is also done with its work.
In my app, I'm doing a network operation in a thread. In the result of the network operation, I show a toast and replace the fragment using the runOnUiThread() method. But the app gets hanged after the fragment is replaced.
Here is my code
getBaseActivity().runOnUiThread(() -> {
hideProgressDialog();
showToastAlertDialog(getString(R.string.mystring));
MyFragment fragment = new MyFragment();
getBaseActivity().replaceFragment(getActivity(), fragment, false, R.id.baseFragment);
BaseActivity.java
public void replaceFragment(FragmentActivity activity, Fragment fragment, boolean addToBackStack, int baseFragment) {
try {
FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction();
ft.replace(baseFragment, fragment);
if (addToBackStack) {
ft.addToBackStack(null);
}
ft.commit();
} catch (Exception exception) {
CXLog.e(TAG, "Exception: " + exception.getLocalizedMessage(), exception);
}
}
Enable strict mode in application and check where your main thread is getting blocked.
Try this: getActivity().runOnUiThread(new Runnable...
It's because:
1) the implicit this in your call to runOnUiThread is referring to AsyncTask, not your fragment.
2) Fragment doesn't have runOnUiThread
Note that this just executes the Runnable if you're already on the main thread, and uses a Handler if you aren't on the main thread. You can implement a Handler in your fragment if you don't want to worry about the context of this, it's actually very easy:
http://developer.android.com/reference/android/os/Handler.html
I am writing an android app that transforms a nexus 7 tablet into an info panel which should show certain information in fullscreen - no user interaction is necessary.
For each type of information, i created a fragment with the corresponding UI. What i want to achieve is that the main activity switches through the fragments each 5 seconds.
Within each fragment, I implemented a simple callback telling the Activity that its AsyncTask finished getting the data and building the (fullscreen) UI:
((MainActivity)getActivity()).fragmentFinishedLoading();
Within my activity, the method looks like this:
public void fragmentFinishedLoading(){
finishedFragments++;
while(finishedFragments == amountOfFragments){
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.fragment_A));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.fragment_B));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.fragment_C));
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.fragment_D));
fragmentTransaction.commit();
fragmentManager.executePendingTransactions();
Thread.sleep(5000);
}
}
Note that the code above did not yet include functionality to dynamically change which fragment should be hidden or showed.
My problem: after
fragmentTransaction.commit();
and even after
fragmentManager.executePendingTransactions();
the fragments did not change at all. I checked the commit() method and found out that it is async. I know that the Thread.sleep() is bad practice since it freezes the UI, but since no UI interaction is necessary i could live with that. However, since i sent the UI thread to sleep, probably the fragmentTransaction sleeps as well(?)
I am searching for a way to hide/show a certain amount of fragments after a given time without any user interaction - any help on that is greatly appreciated.
EDIT:
rguerra's CountDownTimer suggestion did the trick. For the sake of transparency, this is the solution that works for me:
public void fragmentFinishedLoading(){
finishedFragments++;
if(finishedFragments == amountOfFragments){
new FragmentTimer(5000, 1000, 1).start();
}
}
private class FragmentTimer extends CountDownTimer{
int fId;
static final int amountOfFragments = 4;
public FragmentTimer(long millisInFuture, long countDownInterval, int fragmentId) {
super(millisInFuture, countDownInterval);
fId = fragmentId;
}
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if(fId == 1){
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.A_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.B_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.C_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.D_fragment));
}
else if (fId == 2){
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.A_fragment));
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.B_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.C_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.D_fragment));
}
else if (fId == 3){
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.A_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.B_fragment));
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.C_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.D_fragment));
}
else if (fId == 4){
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.A_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.B_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.C_fragment));
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.D_fragment));
}
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentTransaction.commit();
fragmentManager.executePendingTransactions();
if(fId == amountOfFragments){
fId = 1;
}
else{
fId++;
}
new FragmentTimer(5000, 1000, fId).start();
}
}
Use replace Fragment.
example for show R.id.fragment_D
new CountDownTimer(5000, 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.R.id.fragment_D, (Fragment) new FragmentAFragment(), FRAGMENTA_TAG);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
}.start();
http://developer.android.com/reference/android/os/CountDownTimer.html
There are several ways to do this. You can use a Handler OR Alarm Manager with a PendingIntent OR a Timer.
Based on what your design sounds like I would suggest a ViewPager might be a good choice for managing the transitions between your fragments.
Handler mHandler;
public void useHandler() {
mHandler = new Handler();
mHandler.postDelayed(mRunnable, 5000);
}
private Runnable mRunnable = new Runnable() {
#Override
public void run() {
mMyFragmentStatePagerAdapter.myViewPagerIncrementFunction();
useHandler();
}
};
Cancel it with: mHandler.removeCallbacks(mRunnable);
i am trying to reload ListFragment from AsyncTask like this :
//set the string of the kitchen banner depending on the
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
RequestNewTopBannerFragment requestNewTopBannerFragment = new RequestNewTopBannerFragment();
Bundle argsBanner = new Bundle();
argsBanner.putString(RequestNewTopBannerFragment.ARG_KEY_BANNER_TEXT_STR, crrentTopBannerString);//this.currentRequestType ContractFoodServices.NEW_REQUEST_CONST;
requestNewTopBannerFragment.setArguments(argsBanner);
transaction.replace(R.id.fragment_container_top_banner_kitchen, requestNewTopBannerFragment) ;
transaction.addToBackStack(null);
transaction.commit();//LIGHT TIGER : commitAllowingStateLoss()
}
but although i am doing this in function "onProgressUpdate" but i have get exception "IllegalStateException: Can not perform this action after onSaveInstanceState"
of course if i change the function "commit" to "commitAllowingStateLoss" it shows the same exception , and at the same time , the Fragment is not reloaded all the times , some times it works and others not.
i tried to use another way to reload the ListFragment from thread so i tried this code :
runOnUiThread(new Runnable() {
public void run() {
RequestNewTopBannerFragment requestNewTopBannerFragment = new RequestNewTopBannerFragment();
Bundle argsBanner = new Bundle();
argsBanner.putString(RequestNewTopBannerFragment.ARG_KEY_BANNER_TEXT_STR, crrentTopBannerString);//this.currentRequestType ContractFoodServices.NEW_REQUEST_CONST;
requestNewTopBannerFragment.setArguments(argsBanner);
transaction.replace(R.id.fragment_container_top_banner_kitchen, requestNewTopBannerFragment) ;
transaction.addToBackStack(null);
transaction.commit();//LIGHT TIGER : commitAllowingStateLoss()
}
});
but it did not work too , and show the same exception , so what is the solution to be able to reload the FragmentList at all time from "AsyncTask"
thanks in advance
I have an android application with different activities and they all pull data from a web source. This is done by implementing Runnable and creating a thread with the activity as object. The basic class looks like this:
public ActivityX extends Activity implements Runnable {
#Override
public onResume() {
super.onResume();
Thread someThread = new Thread(this);
someThread.start();
}
#Override
public run() {
try {
// pull web content
}
catch(TimeOutException e) {
// >>> create dialog here <<<
// go back to another activity
}
}
}
I tried to create a dialog helper class with a static method that returns the timeout dialog and then call show() like this:
HelperClass.getTimeOutDialog().show();
but the problem is, I can't call it from inside the run() method, as it's in a different thread. If I try to, I will get a runtime exception stating:
Can't create handler inside thread that has not called Looper.prepare()
I need to do this dialog for nearly a dozen of activities and I really want to get around using a Handler objects and sending a message to call the dialog every time. Isn't there an easier way to do this? I just can't think of any right now unfortunately.
My code would look something like this:
handler.handleEmptyMessage(1);
This is to call the handler. And the following would handle the message:
private Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
if(msg.what == 1) {
// show dialog here
}
}
};
Cheers
#Override
public run() {
try {
// pull web content
}
catch(TimeOutException e) {
runOnUiThread(new Runnable(){
#Override
public void run() {
// >>> create dialog here <<<
// go back to another activity
}
}
}
}
Try the one above if you don't want to use Handler.
private Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
if(msg.what == 1) {
// show dialog here
}
}
};
Is this code a part of your activity and not in a thread? If it is a part of your non Ui thread, it would give you the error message. Make sure the handler instance is created in your UI thread because a handler contains an implicit reference to the thread they get created in.