Call a method inside a fragment when all UI elements are visible - java

Is there a way of calling a method inside a fragment once all UI elements are displayed? I am having trouble at the moment trying to figure out which method in the fragment lifecycle should I put my code in. Basically, I am waiting for some input on my socket by calling inStream.read(buffer) where inStream is an inputStream and buffer is a byte[] already initialised. My problem is that this call prevents the UI from being fully displayed whether I call it in onResume, onStart, onActivityCreated (it always waits until it has received something before the UI is fully displayed). Is there a specific fragment lifecycle method for this? (So the socket kinda works in the background just waiting for input instead of hogging the foreground, since the data received will be used to fill some textboxes in the UI of the fragment).

You are blocking the UI Main thread who is responsible for drawing UI components, open and read your Socket in a separeated Thread like AsyncTask

sockeqwe is spot on, you should perform that task in a separate thread so as not to block the UI thread.
To answer the question in the title in case anybody arrives here for that reason, a global layout listener can be used to get a callback when layout is complete. In onCreateView use any view in your layout:
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// do whatever you need
// remove the layout listener if not needed anymore
removeOnGlobalLayoutListener(view, this);
}
});
Here's a static helper method to remove the layout listener
public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener victim) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
removeLayoutListenerJB(v, victim);
} else removeLayoutListener(v, victim);
}
#SuppressWarnings("deprecation")
private static void removeLayoutListenerJB(View v, ViewTreeObserver.OnGlobalLayoutListener victim) {
v.getViewTreeObserver().removeGlobalOnLayoutListener(victim);
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private static void removeLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener victim) {
v.getViewTreeObserver().removeOnGlobalLayoutListener(victim);
}

Related

Calling a method in Custom View from a HandlerThread

Hi all I am a beginner in android programming and I am having a little trouble trying to understand how HandlerThread works. Specifically, I am not sure if the method in the custom view class is executed in the background thread (or non-UI thread) whenever I call the method in a runnable that is added to the thread's message queue.
I have a custom view and HandlerThread initialized in mainactivity:
HandlerThread mainHandlerThread = new HandlerThread("mainhandler");
mainHandlerThread.start();
Handler mainHandler = new Handler(mainHandlerThread.getLooper());
myCustomView mcv = (MyCustomView) findViewById(R.id.customView);
And in MyCustomView class (which extends view), i have a method called update():
public void update(int number, String txt) {
//perform some calculations
invalidate(); //redraw the view
}
Everytime MainActivity detects a change, it will use mainHandler.post() to call MyCustomView's update method:
mainHandler.post(new Runnable() {
#Override
public void run () {
mcv.update(123,"test")
}
});
Does the above code cause the custom view to be redrawn from the HandlerThread(which is a non-UI thread)? I was able to draw the view using both invalidate() and postInvalidate(), hence I am confused on whether the update() method is running on the UI-thread or on the HandlerThread I created.

Call a method when fragment is visible to user

I need execute a method when the fragment is visible (to the user).
Example:
I have 2 buttons (button 1 and button 2) ,
2 fragments(fragment 1 and fragment 2)
and the method loadImages() inside the class fragment 2.
when I press "button2" I want to replace fragment 1 by fragment 2
and then after the fragment 2 is visible (to the user) call loadImages().
I tried to use onResume() in the fragment class but it calls the method before the fragment is visible and it makes some delay to the transition.
I tried setUserVisibleHint() too and did not work.
A good example is the Instagram app. when you click on profile it loads the profile activity first and then import all the images.
I hope someone can help me. I will appreciate your help so much. Thank you.
Use the ViewTreeObserver callbacks:
#Override
public void onViewCreated(View v, Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
final View view = v;
// Add a callback to be invoked when the view is drawn
view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
#Override
public void onDraw() {
// Immediately detach the listener so it only is called once
view.getViewTreeObserver().removeOnDrawListener(this);
// You're visible! Do your stuff.
loadImages();
}
});
}
I'm a little confused by what you are trying to do. It sounds like the images are loading too fast for you... so does that mean that you have the images ready to display? And that is a bad thing?
My guess (and this is just a guess) is that Instagram does not have the profile pictures in memory, so they have to make an API call to retrieve them, which is why they show up on a delay. If the same is the case for you, consider starting an AsyncTask in the onResume method of the fragment. Do whatever loading you need to do for the images in the background, and then make the images appear in the onPostExecute callback on the main thread. Make sure you only start the task if the images are not already loaded.
However, if you already have the images loaded in memory, and you just want a delay before they appear to the user, then you can do a postDelayed method on Handler. Something like this:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
loadImages();
}
}, 1000);
Edit
As kcoppock points out, the handler code is pretty bad. I meant it to be a quick example, but it is so wrong I should not have included it in the first place. A more complete answer would be:
private Handler handler;
public void onResume(){
super.onResume();
if(handler == null){
handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
loadImages();
}
}, 1000);
}
}
public void onDestroyView(){
super.onDestroyView();
handler.removeCallbacksAndMessages(null);
handler = null;
}
Use the onActivityCreated() callBck

Android animation only shows the last ArrayList element

I have 5 TextView fields that are meant to display a String and change the text every 10 seconds.
In order to make the change I have an AsyncTask that grabs the next 5 messages (doInBackground) and passes them to onPostExecute to be displayed in the UI.
When I display the messages without an animation, everything works perfectly fine and the messages rotate every 10 seconds via a new AsyncTask every time.
Now, when I attempt to use a simple fade-in/out animation on the TextViews, I can see all of them fading out and then back in with a placeholder string however the actual text messages I want to display are only displayed in the last TextView.
The AsyncTask is an inner-class of my MainActivity.java.
I have implemented an AnimationListener in MainActivity.java and due to that the objects below in onPostExecute ((TextView) mNextTextView & (String) mNextMessageToDisplay) are also declared as global variables in the main class.
Here's AsyncTask's onPostExecute:
protected void onPostExecute(List<String> mFiveMessagesToDisplay) {
super.onPostExecute(mFiveMessagesToDisplay);
for (int mTextViewCounter = 0; mTextViewCounter < 5; mTextViewCounter++) {
// Prepare the next TextView and message to use
mNextTextViewToUse = mTextViewArrayList.get(mTextViewCounter);
mNextMessageToDisplay = mFiveMessagesToDisplay.get(mTextViewCounter);
// Post and animate the message changes in the TextView
mNextTextViewToUse.startAnimation(mFadeOutAnimation);
}
}
And my AnimationListener:
private AnimationListener mFadeOutAnimListener = new AnimationListener() {
public void onAnimationStart(Animation animation) {}
public void onAnimationRepeat(Animation animation) {}
public void onAnimationEnd(Animation animation) {
mNextTextViewToUse.setVisibility(View.INVISIBLE);
mNextTextViewToUse.setText(mNextMessageToDisplay);
mNextTextViewToUse.setVisibility(View.VISIBLE);
mNextTextViewToUse.startAnimation(mFadeInAnimation);
}
}
As you can see, my setText() method is applied on the mNextTextViewToUse object with the contents of mNextMessageToDisplay, which are as mentioned before both global variables in the main class.
Both ArrayLists, the one containing the messages and the other containing the correct, I have iterated over both and printed the contents - everything is in its place.
Reminding again that if I use setText() with the same TextView and String objects under onPostExecute, everything works perfectly fine just without the animation.
Many thanks in advance!
Your issue is that the loops finishes by the time the animation ends, that's why you see only the last message.
Lets say that mNextTextViewToUse points to TextView1. You start a fade out animation for this TextView. By the time the animation ends, mNextTextViewToUse points to a different textView object (because the loop continue to run while the fade out animation is playing) so when onAnimationEnd is being called (by the fade out animation of TextView1) mNextTextView points to TextView5, and basically you are setting the text to this text view only.
There are several ways to solve this / implement what you need, personally I would probably create my own TextView class (extending TextView of course) and I'll add a public method called setTextAnimated (String newText). It will look something like:
public void setTextAnimated (String newText) {
mNextText = newText;
startAnimation(mFadeOutAnim);
}
where your onAnimationEnd callback will simple call setText(mNextText) and then will start fade-in animation.
Using this you will avoid another issue - that if you use the same animation object on different views - they will share the same timeline and interpolator. The result is that if View1 start a fade out animation that should take 200ms, and View 5 starts the same animation after 140ms, View5 will immediately "jump" to the same alpha value that View1 is.
I found the solution!
I ended up completely removing the FOR loop logic and using recursion which raises a Boolean flag once it has used all 5 available TextViews.
The method that gets the next TextView and String message and execute the animated setText():
public void updateTextView(List<String> mMessages) {
mNextTextViewToUse = mTextViewArrayList.get(mCurrentMessageIndex);
mNextMessageToDisplay = mMessages.get(mCurrentMessageIndex);
mNextTextViewToUse.startAnimation(mFadeOutAnimation);
if (mCurrentMessageIndex == 4) {
mStopMessageDisplayRepeat = true;
} else {
mCurrentMessageIndex++;
}
}
The Runnable that calls the method above, while the recursion stop Boolean is false:
public Runnable mUpdateTextViewRunnable = new Runnable() {
final static int POSTDELAY_INTERVAL = 600;
#Override
public void run() {
updateTextView(mFiveMessages);
if (! mStopMessageDisplayRepeat) {
mMessageDisplayRepeatHandler.postDelayed(mUpdateTextViewRunnable, POSTDELAY_INTERVAL);
}
}
};
Execute the Runnable:
public void startTextViewRunnableRepeat() {
mUpdateTextViewRunnable.run();
}
This is how my AsyncTask's onPostExecute() looks like:
#Override
protected void onPostExecute(List<String> mFiveMessagesToDisplay) {
super.onPostExecute(mFiveMessagesToDisplay);
mFiveMessages = mFiveMessagesToDisplay;
startTextViewRunnableRepeat();
}

Android TextView.setText() invoked & returned before Thread.sleep() blocks until sleep() returns. Why?

In the Android framework, if a TextView's setText() method is called, and after it returns Thead.sleep() is called, then the screen of the device does not display the given text until after sleep() has returned. Why?
public class SleeperActivity extends Activity {
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.main);
}
public void doDelay(View view) {
try {
TextView textView = (TextView) view;
textView.setText("Sleeping");
Log.d("Sleeper", "This log entry is invoked before sleeping");
Thread.sleep(5000L);
textView.setText("Ready");
} catch (InterruptedException e) { finish(); }
}
}
The layout, not shown, has a button whose onClick attribute is set to doDelay().
When the above code is compiled and run, and the button is clicked, the text passed in the first invocation of setText() does not appear on the screen until after the thread has slept for five seconds, even though the log entry appears in the log before those five seconds begin to elapse.
Why does this happen, and what ought I to do about it to make the text passed in the first call to setText() appear on the screen before the thread begins to sleep?
1)Never sleep that long on the UI thread. In fact, never sleep on it at all. That causes it to block and makes the phone unresponsive
2)setText calls invalidate on the view. That will cause the framework to draw the view again as soon as its able- but that means needs to finish the current things its doing and get back to the main Looper (basically, it needs to return from whatever method in your code it first calls). Until then, the change won't be visible on screen.
Edit: to put it another way, inside the framework we have
do{
Message msg = nextMessage();
if(msg.what == ON_CREATE){
currentActivity.onCreate();
}
else if(msg.what == DRAW){
rootView.onDraw();
//draw entire screen
}
else
... //Thousands of more events like touches, handlers, etc
It can't get to the draw message until its done with the current message. Its single threaded.
The reason for the observed behavior is that Views are only redrawn after the completion of each cycle through the event loop. The android onCreate() method, as well as touch events such as clicking a button, are invoked by the framework from within the event loop. Thus, methods such as onCreate() or onClick() will execute to completion before any Views are redrawn, and there will be no visible effect on the display until after those methods have returned. Changes to Views made from within those methods will only become visible after completion of the event loop cycle from within which the framework invoked said method (onCreate(), onClick(), etc.).
To achieve the behavior requested in the question, invocation of the sleep() method must occur after completion of the event loop cycle in which you invoked setText() with the text you want to be displayed during time the thread is blocking. One way to do this is simply to replace the call to sleep() with a call to View.post(). View.post() takes a Runnable whose run() method will be called after the current cycle of the event loop has completed and the framework has displayed the text you want to see during the time the thread is blocking. Change the doDelay() method in the question by placing the call to Thread.sleep() inside a Runnable's run() method like this:
public void doDelay(View view) {
final TextView textView = (TextView) view;
textView.setText("Sleeping");
textView.post(new Runnable() {
public void run() {
try {
Log.d("Sleeper", "Thread " + Thread.currentThread() + " going to sleep...");
Thread.sleep(5000L);
Log.d("Sleeper", "Thread " + Thread.currentThread() + " now awake");
textView.setText("Ready");
} catch (InterruptedException e) { finish(); }
}
});
}
My thanks goes to to Gabe Sechan, whose guidance got me unstuck enough to answer my own question.

Showing a progressdialog in android while a large method is executed

I have a long running method that is called during onCreate, this method populates textviews so interacts with the UI, and updates maybe 70 labels (about 3-20 seconds depending on device).
I want to display a progressdialog as this method executes.
Ideally I want to fire my method on the UI thread once the Activity has been displayed and the progress is displayed, this I cannot do, the Activity won't paint until the method has finished.
I hoped to find an event which was fired after the activity was displayed, and I found the one below, but it still leaves the screen black until the method has finished.
#Override
public void onPostCreate(Bundle savedInstanceState)
I am normally a WP7 developer, and in .NET you add an event handler for onLoadComplete which is fired after the ui is displayed, but before the user has a chance to interact withthe UI, how do I do this in Android JAVA?
Thanks
Put a ProgressBar in the View.
Then in the onCreate() or onResume() method do this:
new Thread() {
public void run() {
yourLargeMethod();
}
}.start();
Now you can do this inside your method to update the progressBar
public void yourLargeMethod() {
// doSomething
...
runOnUiThread(new Runnable() {
public void run() {
// update the progress from the thread
progressBar.setProgress(x); // x is your progress, 0 <= x <= progressBar.getMax()
}
});
}

Categories

Resources