Adding nested views dynamically freezes the App - java

I am building the app, which generates and adds view dynamically. I don't know in advance what views I need to create, these can be nested layouts or simple labels, etc, depending what comes back from web services.
Everything has been well so far until I started building really complex nested layouts .I have one case where I need to add about 11 levels of Layouts dynamically. When activity starts I display ProgressDialog(ring), while views are being generated. My problem is that with this complex structure ProgressDialog freezes while views are added. This is the code, which creates the view:
private class ViewCreator implements Runnable {
public BackgroundTaskViewCreatedResponse delegate;
private View mCreatedView;
private ComponentDefinition mComponent;
private ViewCreator(ComponentDefinition component){
this.mComponent = component;
}
#Override
public void run() {
try {
if (mComponent != null){
mComponent.setLinkedData(model.getLinkedData());
mCreatedView = componentCreator.createComponent(mComponent);
}
} finally {
if (mCreatedView != null)
delegate.processFinishTask(mCreatedView);
}
}
}
Layout, which has other views in it implements BackgroundTaskViewCreatedResponse, so, when view is ready, it will be added:
#Override
public void processFinishTask(final View createdView) {
//((Activity)view.getContext()).runOnUiThread(new Runnable(){
mView.post(new Runnable(){
#Override
public void run() {
mView.addView(createdView);
}
});
}
As you can see above, I have tried to call runOnUiThread call, but this blocks the UI thread completely while view hierarchy is being generated. At the same time view.post doesn't get called out of the box, so I have made some changes to views as suggested in this SO answer. So, now my views are added, but my ProgressDialog is not running smoothly. It stops in a few occasions and then resumes. I've also tried using Android AsyncTask, but that gives the same effect as runOnUiThread
I am not very experienced with Threads, have been trying to fix this for a few days now. Please help.

You can use AsyncTask to do this/ Here is an example:
private class GenerateViews extends AsyncTask<Void,Void,Void>{
#Override
protected void onPreExecute() {
// SHOW THE SPINNER WHILE GENERATING VIEWS
spinner.setVisibility(View.VISIBLE);
}
#Override
protected Void doInBackground(Void... params) {
//CALL YOUR VIEW GENERATING METHOD HERE
return null;
}
#Override
protected void onPostExecute(Void result){
spinner.setVisibility(View.INVISIBLE);
}
}
You can make this class inside your class, if you want to. And then, you just call
new GenerateCalls.execute();

Related

Leanback library - PlaybackSupportFragment - how to hide secondary actions

I'm working on an Android TV app that plays a video and I'm using PlaybackSupportFragment to implement the on-screen controls.
I am, however, not able to fully hide the secondary actions (thumbs up, thumbs down, repeat, ...). Actually I was able to hide the buttons, however the "three dots" button still stays there. And I have not figured out how to hide this "three dots" button.
I tried not to add any secondary actions buttons. But no luck. There are no secondary buttons but the "three-dots" button is still there.
public class MySupportFragment extends PlaybackSupportFragment {
MyPlaybackControlGlue myGlue = new MyPlaybackControlGlue(context);
...
myGlue.setHost(new PlaybackSupportFragmentGlueHost(this));
...
}
public class MyPlaybackControlGlue extends PlaybackControlGlue{
...
#Override
protected void onCreateSecondaryActions(ArrayObjectAdapter adapter) {
//Do not add any secondary actions to the adapter
}
#Override
protected void onCreatePrimaryActions(SparseArrayObjectAdapter adapter) {
//No need to add any other actions here. The Play/Pause action is enough.
}
}
I think I figured it out.
Do not add any secondary actions in the "onCreateSecondaryActions" method.
But also call following method so that the "three dots" button is not shown.
getControlsRowPresenter().setSecondaryActionsHidden(false)
For example like so
#Override
protected void onCreateControlsRowAndPresenter() {
super.onCreateControlsRowAndPresenter();
getControlsRowPresenter().setSecondaryActionsHidden(false);
}
The code that have you posted in the question worked for me, however I am extending VideoPlayerGlue
public class CustomPlaybackControlGlue extends VideoPlayerGlue {
public CustomPlaybackControlGlue(Context context, LeanbackPlayerAdapter playerAdapter, OnActionClickedListener actionListener) {
super(context, playerAdapter, actionListener);
}
#Override
protected void onCreatePrimaryActions(ArrayObjectAdapter adapter) {
//Do not add any secondary actions to the adapter
}
#Override
protected void onCreateSecondaryActions(ArrayObjectAdapter adapter) {
//No need to add any other actions here. The Play/Pause action is enough.
}
}

How to write code between onCreate and onStart?

Several times I've had problems writing code on onCreate(). Mostly because the UI has not been sized and laid out on the screen yet (even if I place my code at the end of the function). I've looked over the activity life-cycle to see if there's anything that runs after onCreate(). There is onStart(), but the problem is that onRestart() recalls onStart(), I don't want that. So is there a way to write code between onCreate() and onStart()? OR where should I write code that runs after the UI is placed and only runs once during its process?
Not sure what exactly you need but you can "cheat" and simply store whether you have run code or not:
private boolean mInit = false;
void onStart() {
if (!mInit) {
mInit = true;
// do one time init
}
// remaining regular onStart code
}
The other way of running code when UI is placed is to use the global layout listener:
public class FooActivity extends Activity implements ViewTreeObserver.OnGlobalLayoutListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_content);
View content = findViewById(android.R.id.content);
content.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
#Override
public void onGlobalLayout() {
// unregister directly, just interested once.
View content = findViewById(android.R.id.content);
content.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// do things here.
}
}

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 acessing MainActivity from AsyncTask

My application crashes after click the button, but the code executes properly.
public void makeLead(View v) throws Exception {
try {
RegisterTimer rt = new RegisterTimer();
rt.ma = this;
rt.execute(null);
} catch (Exception e) {
e.printStackTrace();
}
}
public void log(String msg)
{
final TextView tv = (TextView)findViewById(R.id.editText);
tv.append(msg);
}
private class RegisterTimer extends AsyncTask {
public MainActivity ma;
#Override
protected Object doInBackground(Object[] objects) {
ma.log("ausd");
return null;
}
}
makeLead is onClick event. Method ma.log generates an error but works properly (msg added to textEdit). When I delete ma.log, app doesn't crash. I have no logs in my AndroidStudio so I can't see error message. What's wrong ?
You can not touch the Views in a non UI Thread.
and you are appending text to TextView in a background Thread which is not allowed.
and I hope there is no problem with the initialization of MainActivity inside RegisterTimer as you are not creating the instance of Activity manually. You are in correct way with the initialization rt.ma = this. and why do you need AsyncTask just for changing the text of a TextView?
You cannot update ui from a doInbackground. Initializing ma is not required
Make AsyncTask an inner class of Activity and update ui in onPostExecute
or use interface as a callback to the activity
Edit:
To make it clear
Make asynctaks an inner class of activity. Declare textview as a instance variable. Return result in doInbackground
In Activity
TextView tv;
#Override
public void onCreate(Bundle savedInstancestate)
super.onCreate(savedInstancestate);
setContentView(R.layout.yourlayout);
tv = (TextView)findViewById(R.id.editText);
Then
#Override
protected String doInBackground(Void objects) {
// some background computation
return "ausd";
}
In onpostExecute
#Override
protected void onPostExecute(String result)
{
super.onPostExecute();
tv.append(result);
}
Also you need
private class RegisterTimer extends AsyncTask<Void,Void,String> { // args missing
As described by #Raghunandan you have not initialized ma.
next is you cannot access view in background thread.
if your thread class is inside of MainActivity class then you can use
runOnUiThread(new Runnable() {
#Override
public void run() {
ma.log("ausd");
}
});
inside doInBackground method to update view.
Your method log is public, you don't need to make an object of the MainActivity class to access it, instead you can call it directly. Also you need to add some template after your ASYNC task, if you want to pass some input to your background process, you are using ASYNC task in a wrong way.

How to make a callback after the view is completely rendered?

How to make a callback after the view is completely rendered ?
I am trying to call a method which takes a screen-shot of parent view.
If I write that code in onCreate() method, the app crashes due to null pointer (as no view is rendered).
for now the temporary solution I have implemented is to make a delay of 1 second before calling that method.
But, I am looking for a much more robust solution to this problem.
any suggestions and help appreciated.
thanks :)
Try this logic ... always called after the view has got focus or rendered or looses focus
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
doWhateverAfterScreenViewIsRendered();
}
I have got a better option now :)
this really gives a call-back after the layout is rendered
private class LoadActivity extends AsyncTask<String, Void, String> {
#Override
protected void onPreExecute() {
super.onPreExecute();
mainMethod();
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
takeScreenShot(1);
}
#Override
protected String doInBackground(String... params) {
return null;
};
}
I created the above class and then called this method for execution in onCreate()
LoadActivity loadactivity= new LoadActivity();
loadactivity.execute();
You should use the onResume() callback on your Activity, which is
Called when the activity will start interacting with the user. At this point your activity is at the top of the activity stack, with user input going to it.

Categories

Resources