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);
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 facing a strange issue which I am not able to solve. The thing is, I have an activity and after pressing a button I change the visibility of some layouts. After that I am using a Handler in order to revert that situation after 4 seconds, and put everything as was before.
Everything works as expected except when I change the device orientation, if I change the device orientation during the process, my views will not be restored, I am not sure where the problem is :S.
Here is the relevant code
private View mLoginFormView;
private View mLoginStatusView;
private boolean mLogginIn = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_layout);
mLoginFormView = findViewById(R.id.login_form);
mLoginStatusView = findViewById(R.id.login_status);
if (savedInstanceState == null) {
mLogginIn = false;
} else {
mLogginIn = savedInstanceState.getBoolean(getString(R.string.user_login_in),false);
Log.d(TAG,"RESTORING MLOGIN IN = " + mLogginIn);
}
findViewById(R.id.sign_in_button).setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View view) {
attemptLogin();
}
});
if(mLogginIn)
showProgress(true);
}
Now I present the relevant functions
//.......
public void attemptLogin() {
showProgress(true);
mMenu.findItem(R.id.started).setVisible(false);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
showProgress(false);
Log.d(TAG,"MLOGGIN VALUE PROGRESS = " + mLogginIn);
mLogginIn = false;
}
});
}
},4000);
}
And ShowProgress()
private void showProgress(final boolean show) {
mLogginIn = show;
Log.d(TAG, "showProgress " + mLogginIn);
int shortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);
mLoginStatusView.setVisibility(View.VISIBLE);
mLoginStatusView.animate().setDuration(shortAnimTime)
.alpha(show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mLoginStatusView.setVisibility(show ? View.VISIBLE
: View.GONE);
}
});
mLoginFormView.setVisibility(View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE
: View.VISIBLE);
}
});
}
The most weird thing, is that when onRestoreInstanceState is called after the postDelayed code has been executed, mLogginIn shows the previous value (true), as if it had no effect at all.
Thanks in advance
When your Activity is recreated because of a configuration change, it is a completely different object instance, with another embedded view hierarchy. Your Handler is tight to the old object instance, and thus it modifies the old view hierarchy too. Therefore you don't see anything.
Don't do this: it leaks memory and might lead to crashes.
Solution:
When you post your Runnable, save the Runnable instance and the current timestamp in two references. If the Runnable runs normally, set the timestamp to -1 (within the Runnable code).
On onSaveInstanceState(Bundle) if the timestamp is different from -1, retain it across configuration changes and remove the runnable callbacks from the handler with Handler.removeCallbacks(Runnable).
When you recreate the activity restore the timestamp and calculate the difference with the current time. Post a new Runnable with the same logic with this delay (if it is 0 or negative you might want to run the runnable straight away instead of posting it). This Runnable is now tight to the new instance of Activity.
When device orientation is changed your activity is destroyed and new one is created and state restored via savedInstanceState bundle. But your runnable you are posting with postDelayed is an anonymous inner class tied to original activity. This activity object is still around and your runnable modifies it, but it is no longer visible.
Your current architecture does not work, you have to choose another to achieve what you want.
I have a fragment which runs camera in it. and then in the main activity, I instantiate the class by passing the context to it
Camera_play fragment1 = new Camera_play(this);
I have other fragments which play images and videos.
I rotate the device after 10 seconds to landscape mode and I need all the fragments to keep playing .
Timer timer = new Timer();
TimerTask action = new TimerTask() {
public void run()
{
runOnUiThread(new Runnable() {
public void run(){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
});
}
};
timer.schedule(action, 10000);
I handle the objects as follows :
if(savedInstanceState == null)
{
frg1 = new Camera_play(this);
frg2 = new play_video();
android.support.v4.app.FragmentManager manager= getSupportFragmentManager();
transaction=manager.beginTransaction();
transaction.add(R.id.video, frg1,"frag1");
transaction.add(R.id.camera, frg2,"frag2");
transaction.commit();
}
else
{
**frg1 = new Camera_play(this);** ?????
frg2 = (play_video) getSupportFragmentManager().findFragmentByTag("frag2");
}
This works fine for the video but the app crashes for the video stating the instantation failed and the class msut have an empty constructor.
How do I make the camera to continue playing after I rotate the device?
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)..