Android: Updating the UI from an adapter inside a While Loop - multithreading

I'm using a GridView to display a list of words in a 4-column table. The getView method of my custom adapter checks the width of the word and shrinks it if it doesn't fit. It does this using a recursive check that keeps scaling the text down until it fits.
private void shrinkText(final TextView wv, final String word) {
wv.setTextSize(defaultTextSize);
new Thread(new Runnable() {
#Override
public void run() {
while (wv.getWidth()>0 && wv.getPaint().measureText(word) > wv.getWidth()) {
Logg.d("word too big. Shrink from " + wv.getTextSize()/density + " to " + (wv.getTextSize()/density-1.0f));
wv.setTextSize(wv.getTextSize() / density - 1.0f);
}
}
}).start();
}
Because I'm using a while loop, I am using a new thread to protect against ANR in the unlikely event of an infinite loop. Here's the weird thing: sometimes it works great. And then sometimes I get the following error:
09-26 14:25:31.389 6427-7765/com.myapp.debug E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-7789
Process: com.myapp.debug, PID: 6427
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
I tried putting the setTextSize inside a runOnUiThread statement, but I can't get it to work inside the adapter. Ultimately I just want this to work. I think my options are:
Keep the while loop in the UI thread and somehow safeguard it (how?)
Move the setTextSize call to the UI thread (how?)
Something else?
Thanks for your help!
UPDATE: based on Rustam's answer, I used wv.post to write to the UI. However the need to use the while loop meant that setTextSize had to be done in the same thread as the while condition itself. I switched from evaluating the TextView.getTextSize to the Paint.getTextSize, since I could set the Paint's text size without impacting the UI, and therefore inside the offshoot thread. Jerry-rigged, but it seems to work.
private void shrinkText(final TextView wv, final String word) {
wv.setTextSize(defaultTextSize);
final Paint mPaint = new Paint(wv.getPaint());
new Thread(new Runnable() {
#Override
public void run() {
while (wv.getWidth()>0 && mPaint.measureText(word) > wv.getWidth()) {
Logg.d("word too big. Shrink from " + mPaint.getTextSize()/density + " to " + (mPaint.getTextSize()/density-1.0f));
mPaint.setTextSize(mPaint.getTextSize() - 1.0f);
}
if (wv.getWidth()>0 && wv.getPaint().measureText(word) > wv.getWidth()) {
wv.post(new Runnable() {
#Override
public void run() {
Logg.d(word + " final size=" + mPaint.getTextSize() / density);
wv.setTextSize(mPaint.getTextSize() / density);
}
});
}
}
}).start();
}

try to update like this:
private void shrinkText(final TextView wv, final String word) {
wv.setTextSize(defaultTextSize);
new Thread(new Runnable() {
#Override
public void run() {
while (wv.getWidth()>0 && wv.getPaint().measureText(word) > wv.getWidth()) {
Logg.d("word too big. Shrink from " + wv.getTextSize()/density + " to " + (wv.getTextSize()/density-1.0f));
wv.post(new Runnable() {
public void run() {
wv.setTextSize(wv.getTextSize() / density - 1.0f);
}
});
}
}).start();
}

Related

onLoadFinished() not getting called, but onCreateLoader() is called

I am using CursorLoader to load data from database inside my Fragment, which has a RecyclerView. This fragment is being used inside a ViewPager. The ViewPager is contained inside a ContainerFragment, which in turn is inside an Activity.
The ContainerFragment initializes first 4 loaders out of the 10 required in onActivityCreated(). From the log I can see that the loader(s) are in fact getting created. However, onLoadFinished is not getting called for any of the loaders.
Now the twist comes when I swipe through the ViewPager to the third fragment. Now I see the third loader's onLoadFinished getting called. Now this is probably not called from the fragment but from the PagerAdapter's getItem() method, which in addition to instantiating the fragments, also initializes loaders with their IDs in case they haven't been already.
From all other questions on StackOverflow about onLoadFinished not getting called, I have tried the following solutions which are not working for me:
Using forceLoad()
this.getListView().refreshDrawableState(); -> not tried, and don't understand why this should work. Moreover, I am using RecyclerView.
"Importing the correct class" -> I am using AndroidX, and moreover, the loader does load sometimes. If it was the wrong class, it wouldn't have worked any time right?
"Fragment should use SupportLoaderManager" -> tried replacing in the fragment with getActivity().getSupportLoaderManager. For fragment I think it's just getLoaderManager. It was already working sometimes with just getLoaderManager. (No difference observed)
#onActivityCreated
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
startLoaders();
}
startLoaders()
private void startLoaders() {
while(loaderSeqIndex < CATEGORIES.size() && loaderSeqIndex < 4) {
Bundle bundle = new Bundle();
bundle.putString("category", CATEGORIES.get(loaderSeqIndex));
getLoaderManager().initLoader(loaderSeqIndex++, bundle, this);
}
}
LoaderCallbacks
#NonNull
#Override
public Loader<Cursor> onCreateLoader(int id, #Nullable Bundle args) {
Timber.i("Loader created onCreateLoader called");
CursorLoader loader = ArticleLoader.newCategorizedArticlesInstance(getContext(), args.getString("category"));
loader.registerListener(id, new Loader.OnLoadCompleteListener<Cursor>() {
#Override
public void onLoadComplete(Loader<Cursor> loader, Cursor data) {
int position = loader.getId();
cursorHashMap.put(CATEGORIES.get(position), data);
Timber.i("mPager.getCurrentItem(): " + mPager.getCurrentItem() + " position: " + position);
if (position == mPager.getCurrentItem()) {
ArticleListViewModel model = ViewModelProviders.of(ArticleListContainerFragment.this).get(ArticleListViewModel.class);
model.setCursor(data);
}
}
});
return loader;
}
#Override
public void onLoadFinished(#NonNull Loader<Cursor> loader, Cursor data) {
int position = loader.getId();
cursorHashMap.put(CATEGORIES.get(position), data);
Timber.i("mPager.getCurrentItem(): " + mPager.getCurrentItem() + " position: " + position);
if(position == mPager.getCurrentItem()) {
ArticleListViewModel model = ViewModelProviders.of(this).get(ArticleListViewModel.class);
model.setCursor(data);
// mPagerAdapter.notifyDataSetChanged();
}
}
PagerAdapter's #getView
private class MyPagerAdapter extends FragmentStatePagerAdapter {
...
#Override
public Fragment getItem(int position) {
ArticleListFragment fragment = ArticleListFragment.newInstance();
Bundle bundle = new Bundle();
bundle.putString("category", CATEGORIES.get(position));
bundle.putInt("id",position);
fragment.setArguments(bundle);
// fragment.setCursor(cursorHashMap.get(CATEGORIES.get(position)));
return fragment;
}
...
}
I was expecting timber to print from the #onLoadFinished method just to make sure that it's getting called, which isn't happening.
Something weird that is happening is that:
the cursorHashMap that I am using, get's properly populated I open the app a second time (when refresh doesn't happen). And the cursor get's populated without #onLoadFinished being called.

Why does my RotateTransition throw errors after it runs for the first time?

Warning: This is my first time using threads and my first time trying out an animation. Please bear with me.
I want to rotate an ImageView. I set up a thread for it:
public class ThreadAnimation extends Thread
{
private ImageView iv;
private RotateTransition rt;
public ThreadAnimation(ImageView iv)
{
this.iv = iv;
}
#Override
public void run()
{
while (true)
{
RotateTransition r = new RotateTransition();
r.setToAngle(360);
r.setCycleCount(1);
r.setDuration(Duration.millis(300));
r.setNode(iv);
r.play();
try
{
sleep(100);
} catch (InterruptedException e)
{
return;
}
}
}
}
I call this inside my controller class, upon pressing a Button.
animation.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle (ActionEvent abschicken)
{
ThreadAnimation thread = null; //ANIMATION PIZZA
if (thread == null)
{
thread = new ThreadAnimation(olivenview);
thread.start();
}
}
});
My ImageView olivenview will rotate just like I wanted it to. However it takes quite a long time until it seems to stop (I can see it because the button triggering it still looks triggered for a while) and when I go ahead to press it a second time afterwards, I get a nonstop error stream with a lot of null pointer exceptions. I am very clueless, can anyone help me out? Is this due to my Thread Setup or does the problem lie somewhere else (in code that I didn't post here)?
I believe you do not need threads for this. Notice the .play() method returns immediately and the animation will run in the background.
That being said, try this.
...
//Create your rotation
final RotateTransition r = new RotateTransition();
r.setToAngle(360);
r.setCycleCount(1);
r.setDuration(Duration.millis(300));
r.setNode(iv);
//When the button is pressed play the rotation. Try experimenting with .playFromStart() instead of .play()
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent action) {
r.play();
}
});
...
On an other note I recommend switching to java 8 so that you can use lambda expressions instead of the anonymous class!

TextArea is not updating while zip extraction

I am working on a JavaFx application, there i have a script that extract a zip followed by some other operation like updating files etc.
I want to have a textArea that displays whats going on in background, like "Zip extracting...", "Updating xyz file" etc.
Till now i have tried following way:
MyTask<String> task;
task = new MyTask<String>() {
#Override
protected String call() throws Exception {
File path = new File(exportTo.getAbsolutePath());
updateMessage("Extracting modular app to target directory...");
patcher.unZip(appPath.getAbsolutePath(), path.getAbsolutePath());
if (path.exists()) {
AppInfo info = getAppInfo();
patcher.patchAndroid(info, resourceZip, new File(path.getAbsolutePath() + "/" + appPath.getName().substring(0, appPath.getName().lastIndexOf("."))), this);
showOkAlert("Build completed!");
} else {
showOkAlert("Modular app folder not found");
}
return "";
}
#Override
protected void updateProgress(double workDone, double max) {
patcher.reportLogs(message);
}
private String message;
#Override
public void updateMessage(final String message) {
Platform.runLater(() -> patcher.reportLogs(message));
this.message = message;
//updateProgress(0, 0);
}
};
task.run();
MyTask class
abstract class MyTask<T> extends Task<T> {
abstract public void updateMessage(String message);
}
I have tried using updateProgress method, Platform.runLater() but nothing is working.
All the message i printed in textArea are printed after all operation is done.
Please help.
As javadoc for Task states you need to manually create a Thread to execute your Task:
Thread th = new Thread(task);
th.start();
Currently your task is being run on Application UI thread and blocks UI updates.

Observer won't run update in JavaFX GUI

I read much about the JavaFX GUI Model, Plattform->RunLater and Threads, but I still do not figure out how to get this right. I had a JavaFX GUI which on a button click executed a process and updated a Progress Bar and Label. This was running well with Threading and Platform, but I had to Change this to an Observer Model.
I invoke a Progress Tracker in a Singleton Model, which gets updated by the class executing the process and is Observable. I implemented an Observer as well which should update the two UI Elements.
GUI Controller with Button Event
private void createKeyPressed(ActionEvent event) {
// Make Progressbar visible
pbKeyProgress.visibleProperty().set(true);
if (!Check.keyFileExistant() || cbKeyOverwrite.selectedProperty().get()) {
ProgressTracker.getTracker().addObserver(new ProgressObserver(pbKeyProgress, lblKeyProgress));
Creator.createKey(cbKeyLength.getValue());
} else {
}
}
Progress Observer
public class ProgressObserver implements Observer {
private final ProgressBar progressBar;
private final Label statusLabel;
public ProgressObserver(ProgressBar progressBar, Label statusLabel) {
this.progressBar = progressBar;
this.statusLabel = statusLabel;
}
#Override
public void update(Observable o, Object o1) {
Platform.runLater(() -> {
System.out.println("Tracker set to "+ProgressTracker.getProgress() + " " + ProgressTracker.getStatus());
progressBar.setProgress(ProgressTracker.getProgress());
statusLabel.setText(ProgressTracker.getStatus());
});
}
}
Progress Tracker
public synchronized void setTracker(int currentStep, String currentStatus) {
checkInstance();
instance.step = currentStep;
instance.status = currentStatus;
instance.notifyObservers();
System.out.println(instance.countObservers());
}
Creator
public static void createKey(String length) {
Task<Void> task;
task = new Task<Void>() {
#Override
public Void call() throws Exception {
initTracker(0,"Start");
doStuff();
ProgressTracker.getTracker().setTracker(1,"First");
doStuff();
ProgressTracker.getTracker().setTracker(2,"Second");
// and so on
return null;
}
};
new Thread(task)
.start();
}
The Print within the ProgressTracker gets executed. However, if I add a print within the update of the Observer nothing will be printed. If I check within the Progresstracker, the Observer Count is 1.
Why does the Observer not get notified or execute anything, even if the Notify is called? Did I get the Threading and Execution Modell wrong?
The Progress Bar and the Label will also stay on their initial values.
Don't reinvent the wheel. The JavaFX Properties Pattern is a ready-made implementation of the Observable pattern: there is no need to implement it yourself. Additionally, Task already defines methods for updating various properties, which can be called from any thread but will schedule the actual updates on the FX Application Thread. See updateProgress() and updateMessage(), for example.
So you can do, for example:
public static Task<Void> createKey(String length) {
Task<Void> task;
task = new Task<Void>() {
final int totalSteps = ... ;
#Override
public Void call() throws Exception {
updateProgress(0, totalSteps);
updateMessage("Start");
doStuff();
updateProgress(1, totalSteps);
updateMessage("First");
doStuff();
updateProgress(2, totalSteps);
updateMessage("Second");
// and so on
return null;
}
};
new Thread(task)
.start();
return task ;
}
and
private void createKeyPressed(ActionEvent event) {
// Make Progressbar visible
pbKeyProgress.visibleProperty().set(true);
if (!Check.keyFileExistant() || cbKeyOverwrite.selectedProperty().get()) {
Task<Void> task = Creator.createKey(cbKeyLength.getValue());
pbKeyProgress.progressProperty().bind(task.progressProperty());
lblKeyProgress.textProperty().bind(task.messageProperty());
} else {
}
}

new Thread, application still running after Stage-close

So I followed this tutorial: https://www.youtube.com/watch?v=gyyj57O0FVI
and I made exactly the same code in javafx8.
public class CountdownController implements Initializable{
#FXML
private Label labTime;
#Override
public void initialize(URL location, ResourceBundle resources) {
new Thread(){
public void run(){
while(true){
Calendar calendar = new GregorianCalendar();
int hour = calendar.get(Calendar.HOUR);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
String time = hour + ":" + minute + ":" + second;
labTime.setText(time);
}
}
}.start();
}
After I close the Window, application/thread is still running in the system. My guess its because the infinite loop, but shouldnt the thread be terminated with application closing?
Second thing is that when I try to set the text for Label I get the error:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:364)
at javafx.scene.Parent$2.onProposedChange(Parent.java:364)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:575)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(LabeledSkinBase.java:204)
at com.sun.javafx.scene.control.skin.LabelSkin.handleControlPropertyChanged(LabelSkin.java:49)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$60(BehaviorSkinBase.java:197)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$$Lambda$144/1099655841.call(Unknown Source)
at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:103)
at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:110)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:143)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
at javafx.scene.control.Labeled.setText(Labeled.java:146)
at application.CountdownController$1.run(CountdownController.java:29)
...yes, I am going to read more about threads, but I would like to know the answer to these questions.
Part I
A thread, when created, runs independent of other threads. You have a new thread which has an infinite loop, which implies, it will keep running forever, even after the stage has been closed.
Normally, using a infinite loop is not advised, because breaking out of it is very difficult.
You are advised to use :
TimerTask
ScheduledExecutorService
You can then call either one of them (based on whatever you are using)
TimerTask.cancel()
ScheduledExecutorService.shutdownNow()
when your stage is closed. You can use something like :
stage.setOnCloseRequest(closeEvent -> {
timertask.cancel();
});
JavaFX API's (thanks to James_D comment's)
These do not need to be explicitly canceled as ScheduledService uses daemon threads and AnimationTimer runs on the JavaFX thread.
ScheduledService
AnimationTimer
Part II
Your second part of the question has been answered time and again in the forum.
You need to be on the JavaFX Application thread to use scene graph elements.
Since you have created a new thread and trying to update label, which is a JavaFX node, it throws the exception. For more information, please visit:
JavaFX error when trying to remove shape
Why am I getting java.lang.IllegalStateException "Not on FX application thread" on JavaFX?
Javafx Not on fx application thread when using timer
With ScheduledExecutorService as far as I am concerned You cant easly set it as deamon and I don't want to play with stage.setOnCloseRequest(closeEvent -> {});
With AnimationTimer I cant do something like Thread.sleep(100) beetween iteration like you suggested because "AnimationTimer runs on the JavaFX thread."
ScheduledService is just quite difficult for me to understand right now...
so, as I was reading and reading about it I came to conclusion that maybe this simple option will be the best:
public class CountdownController implements Initializable{
#FXML
private Label labTime;
#FXML
private Button buttSTOP;
#Override
public void initialize(URL location, ResourceBundle resources) {
Timer timer = new Timer(true); //set it as a deamon
timer.schedule(new MyTimer(), 0, 1000);
}
public class MyTimer extends TimerTask{
#Override
public void run() {
Calendar calendar = new GregorianCalendar();
int hour = calendar.get(Calendar.HOUR);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
String time = hour + ":" + minute + ":" + second;
Platform.runLater(() -> {
labTime.setText(time);
});
}
}
Thanks James_D and ItachiUchiha. It works, let me know if I'am something missing!
EDIT:
I also include code for Counting down the time, as it was my initial aim, maybe someone will find it usefull as well:
public class CountdownController implements Initializable{
#FXML
private Label labTime;
#FXML
private Button buttSTOP;
private Timer timer = new Timer(true); //set it as a deamon
private int iHours = 0,
iMinutes = 1,
iSeconds = 10;
public void initCountdownController(int iHours, int iMinutes, int iSeconds){
this.iHours = iHours;
this.iMinutes = iMinutes;
this.iSeconds = iSeconds;
}
#Override
public void initialize(URL location, ResourceBundle resources) {
buttSTOP.setOnAction(e -> {
buttSTOPAction(e);
});
timer.schedule(new MyTimer(), 0, 1000);
}
private void buttSTOPAction(ActionEvent e) {
timer.cancel();
}
public class MyTimer extends TimerTask{
#Override
public void run() {
String time = iHours + ":" + iMinutes + ":" + iSeconds;
Platform.runLater(() -> {
labTime.setText(time);
});
if(iSeconds < 1)
if(iMinutes < 1)
if(iHours < 1)
this.cancel();
else{
iHours--;
iMinutes = 59;
iSeconds = 59;
}
else{
iMinutes--;
iSeconds = 59;
}
else
iSeconds--;
}
}

Resources