Change view in activity - java

I have an activity that sets a custom created view "onCreated" and a Thread is started, this thread cycles every 23ms and "invalidates" this view so it's onDraw is called almost always.
This view continues in screen until a user clicks on it (onTouchEvent) and view does some proceses and when ready a public flag ins enabled so the activity "knows" when the view was activated and has made it's proceses, in this moment the activity should change it's view to another one custom created, the problem is that because the flag checking is done in the run method of the thread the activity sends a "CallFromWrongThreadException", this is because as far as I understand the "changing" is in another thread (not UI).
I have set the runOnUiThread to overpass this and the srceen goes completly black.
Also I have tried to set the activity to a framelayout in which I add both views at begging and the thread changes visibiliity of the views, no success at all.

All view manipulation must happen on the UI thread. There are a few ways to do this. One is to use AsyncTask to perform your background work, as it has means to run code on the UI thread after the background work has completed.
Another option is to post the code you want to run through the view itself, like this:
mMyView.post(new Runnable() {
public void run() {
mMyView.doSomething();
}
});
This method would work well for toggling the visibility of an existing view.
This is a good overview of these methods: http://android-developers.blogspot.com/2009/05/painless-threading.html

Related

Android MVP best way to handle views with API call

I am having a question regarding how do I update my view or perform a fragment transition when the view goes to background and the API response comes as a callback.
For example I have a loginfragment. User clicks login button and the presenter takes it and gives it Interactor where I have my Rx observer who talks to repository and model and so on. The response comes back in maybe 3 to 5 seconds. Within this time, I press home button and my app goes to background. The presenter receives the response from Interactor callback and now wants to update the view but view is in background.
On login success, I want to make a fragment transition and go to Fragment B. But my view is in background. So when I bring my app back to foreground, I still see login screen although I already got a login success API response.
How do I handle view updates or fragment transition when view goes to background and what is the best way of doing it?
One approach is Model-View-Intent architecture in Mosby, in which the presenter stores the latest state of the view, and the view subscribes to the ViewState Observable in order to update. In such approach the view can subscribe in onStart(), when it is safe to make fragment transactions.
Another possible approach is that each view has a state (ViewState), that holds a queue of commands to the view. If the view is ready (onStart() was already called), then the command is passed to it immediately, otherwise it is saved to a queue and postponed until the view is ready. See how it is realized in Moxy.

Android animation only works if I reschedule it from the UI thread to the UI thread

I perform a network call in which I
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
and then I pass the result to an io.reactivex.function.Consumer, which attempts to animate a view.
These animations worked when I was loading the data locally and synchronously, but broke when I introduced this server call.
Now when I animate the views, it does not change any of the properties at all, but instead just instantly calls the animation complete listener.
I verified that I'm on the main thread with Looper.getMainLooper().isCurrentThread()
Yet the only way I've been able to get the animation working again is to wrap it in AndroidSchedulers.mainThread().scheduleDirect(() -> {})
Which as shown by this piece of code shouldn't actually change anything
final Thread before = Thread.currentThread();
AndroidSchedulers.mainThread().scheduleDirect(() -> {
boolean nothingChanged = before == Thread.currentThread();
//nothingChanged is true
});
Why do I have to reschedule the animation back onto the thread it is already on in order to get it to work?
Edit: RxJava is not involved, something even weirder is going on.
I cut out RxJava and the issue is reproducing even though I put the animation code in onNavigationItemSelected(), which is called by android when the user clicks something, so its definitely in the right thread.
My animation looks like this.
alertBanner.clearAnimation();
alertBanner.animate()
.alpha(0)
.translationY(alertBanner.getHeight())
.setDuration(500)
.setListener(new AnimatorListenerBuilder().setOnAnimationEnd(animator -> {
alertBanner.setVisibility(GONE);
}))
.start();
AnimatorListenerBuilder is just a utility class that wraps AnimatorListener and lets you only define the callbacks that you want. It works for all the other animations in my app and hasn't been changed in months.
I figured it out. It was a race condition.
When the user clicked the button, two things happened:
A new fragment was created, and an animation was started.
However, the fragment had a callback, onAttach() that would, among other things, call .clearAnimation() on the view I was trying to animate.
By rescheduling the animation back onto the main thread, I was making sure that it happened after the fragment callback.
I solved it by getting rid of the callback from the fragment, and putting that logic in the same place as the logic that starts the animation, allowing me to control the execution order.

Suspend Android app's main thread while showing the ProgressDialog

When my Android app is starting up needs to load a lot of data from the database during onCreate in the firstly loaded activity. I would like to show a ProgressDialog for that. ProgressDialog however can't be shown in the main thread, so one must use AsyncTask or new Thread. But that also means, that the activity continues to be initialized as the main thread goes on.
Basically, I need to show the ProgressDialog or a kind of its equivalent while processing in the main thread (not in AsyncTask).
Is there a way to do it?
ProgressDialog however can't be shown in the main thread, so one must use AsyncTask or new Thread.
How do you come to this conclusion? ALL UI stuff is shown in the UI Thread, thus also the ProgressDialog. It needs to be created and invoked inside the UI Thread to work or else your App crashes.
First you need to check on onCreate() if your stuff is already loaded and if not, show a ProgressDialog, load stuff in the background and then do a post in the UI Thread to dismiss the ProgressDialog and show the results.
That's how it usually works.
The Main/UI Thread is responsible for drawing the UI, and hence, the ProgressDialog itself . So you can not block it and hope that he is going to draw the UI. You should move the initialization stuff inside AsyncTask's doInBackgroud, and move on with the other suff after onPostExecuted is called
You should load the the data with the Thread (ASyncTask) you should display your ProgressDialog with "onPreExecute()" update it with "onProgressUpdate()" and finish the dialog with "onPostExecute()" all of them is running on UI thread already.
You will never be able to show progress because your view of activity have not created, because you read from database in onCreate methode after reading the database onCreate method finshes and now your view inflate and so on . . .

Android View Canvas onDraw not performed

I am currently working on a custom View which draws some tiles on the canvas.
Those tiles are loaded from several files and will be loaded when needed.
They will be loaded by an AsyncTask. If they are already loaded they will just be painted on the canvas. This is working properly!
If those pictures are loaded the AsyncTask is firing view.postInvalidate()
The problem is that my custom View is not firing onDraw(Canvas canvas) everytime I fire view.postInvalidate().
The view.postInvalidate only fires onDraw() method the first time when a picture is loaded and then only when I fire this.invalidate() in an onTouchEvent inside my CustomView
Is it possible that a View decides wether it will draw the canvas again or not?
Is there a way to FORCE the View to redraw? I think the invalidate method tells the View that it would be cool if the View would think about redrawing -.-
Is it possible that those invalidate methods have a limit?
I hope anyone of you knows more about this problem.
edit:
I just changed every postInvalidate() to invalidate() because the images are all loaded by an AsyncTask executed from the main Thread
But there is still the problem that an executed invalidate() is not executing the onDraw() method.
I found out that the view.invalidate() is fired by overriding the original method:
#Override
public void invalidate() {
super.invalidate();
Log.d(TAG, "invalidate executed");
}
I don't know what to do now. I'm firing view.invalidate() and view.postInvalidate() but nothing works in absolutely no combination.
There's a bit of a misunderstanding here. The invalidate and postInvalidate method are used to tell the View that it needs to be refreshed and redrawn in the earliest drawing cycle. The difference is that the invalidate method should be called from within the UI Thread and the postInvalidate should be called from outside of the UI Thread.
These methods are briefly described here:
http://developer.android.com/reference/android/view/View.html#postInvalidate()
http://developer.android.com/reference/android/view/View.html#invalidate()
The AsyncTask on the other hand is a class devised especially for the problem you're facing. When you need to perform a big task in the background, asynchronously you need the AsyncTask for that, but! the AsyncTask's callback method is run in the UIThread!
Take a look at the explanation of AsyncTask methods here:
When an asynchronous task is executed, the task goes through 4 steps:
onPreExecute(), invoked on the UI thread immediately after the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.
doInBackground(Params...), invoked on the background thread immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress(Progress...) to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate(Progress...) step.
onProgressUpdate(Progress...), invoked on the UI thread after a call to publishProgress(Progress...). The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.
onPostExecute(Result), invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.
This means that in the onPostExecute method you should try using the invalidate method instead of the postInvalidate method as it is called from the UIThread.
Have you heard of a method call setWillNotDraw(boolean)?
Check it out here
Set it to false and you'll see
Just to save this for the future and possible google searchers ;)
I made a new Class which provides datas for my View. This class extends Thread and runns in the background. It checks which tiles my view has to display.
Long story short: Each time a new tile has to be displayed my provider class collects some update requests.
If 3 or more update requests are "collected" or it is more than 1 second since the first update request the view will get view.postInvalidate().
The problem was that a view ignores some invalidate requests if you fire several of them at once. If you collect those invalidate requests and fire several of them with just one invalidate() you can see the newest information with just a small lag of time - but you can see it ;)
I've solved the problem requesting view.invalidate() or view.postInvalidate() not for invalidating every child view individually (each of these child views incorporated its own AsyncTask), but for invalidating their common parent ViewGroup.
Now in details. I had
public class mViewGroup extends RelativeLayout {...}
and several Views in it, each View as follows:
public class mView extends View {
private mViewGroup mContext = null;
//with a little bit modified standard constructors:
public mView (Context context, mViewGroup ParentViewGroup) {
super(context);
mContext = ParentViewGroup;
}
//and also with an AsyncTask:
private class mViewAsyncTask extends AsyncTask<Integer, Void, Void> {
public mAsyncTask() {
#Override
protected Void doInBackground(Integer... a) {
//which does its work and sometimes publishes progress:
publishProgress();
return null;
}
protected void onProgressUpdate(Void... progress) {
invalidate();
}
}
The calls to invalidate(); sometimes worked and sometimes didn't (may be in cases if calls were made from different AsyncTasks belonging to different mViews but in very close moments of time).
Changing
invalidate(); //i.e. invalidating individual View
to
mContext.invalidate(); //i.e. invalidating the whole ViewGroup
solved the problem.
In my case, target custom View has height = 0 attribute at that time.(My mistake)
and postInvalidate() and invalidate didn't fired onDraw() and Log too.
When I changed height currectly, all problem solved.

runOnUiThread stops Working When Application is Destroyed

So here's what happening: I'm using the runOnUiThread() method to change an Image View from another class when that class receives a message from another phone. I'm calling it by using mainclass.runOnUiThread(new Runnable...) and so on. It works the first time the application is run, but when I hit the back button to exit the application, and then launch the application again, the Image View no longer updates. The logs I took shows me that everything is fine and being run, but the Image View doesn't change. I don't get any errors and it doesn't crash when I run the application, it just doesn't update the Image View. Any idea what might be causing this?
Edit*: So this is what i'm actually using to change the imageView in a class that doesn't extend Activity
peerActivity.runOnUiThread(
new Runnable(){
#Override
public void run() {
Log.v("Thread#", "Thread is running");
Log.v("Thread#2", decodedImage.toString());
// TODO Auto-generated method stub
peerActivity.ivPhoto.setImageBitmap(decodedImage);
Log.v("Thread#3", "Past UI Change");
}
});
Where peerActivity holds the other class reference and ivPHoto is the Image View. What I think I need to do is find a way to kill that thread when the program closes (when I go into the program manager, it still has the Force Close button available). Is there any way to do this?
Edit2*: So I tried a bunch of different approaches to working on the UI Thread and they all give me the same result: The program works perfectly the first time its run but when I hit the back button and close it and the re-open it, the Image View no longer updates. If I force close the app, it works fine again until I close it and re-open it.
Try using View.post(...) instead.
I use new Handler(Looper.getMainLooper()).post(...);
You can even create this from a background thread.
It looks like your Runnable is closing over a reference to your activity. When you press back and then start your activity again, your old activity is destroyed and a new instance is created. Your background thread continues to make changes to the original activity, which no longer exists, therefore making changes to its views has no effect.
You need to either stop the updating thread when the actiivty is paused or stopped as appropriate and restart it when the activty is resumed or started (again, as appropriate) and make sure that you are always updating the activty that is actually visible.

Categories