I am trying to show a progress indicator in some time consuming code but the screen is not refreshed while the code is running. After the code is finished it animates the indicator.
The solution is probably simple for an experienced android developer which I am not.
I created a minimum code application to show the problem
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/black">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:text="Start"
tools:ignore="HardcodedText" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/cpi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:progress="0"
android:useLevel="true"
android:visibility="visible"
app:indicatorColor="#FF0000"
app:indicatorSize="95dp"
app:trackColor="#606060"
app:trackCornerRadius="5dp"
app:trackThickness="16dp"
tools:layout_editor_absoluteX="157dp"
tools:layout_editor_absoluteY="108dp" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java:
package com.hennep.testcircularprogress;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.os.Handler;
import com.google.android.material.progressindicator.CircularProgressIndicator;
public class MainActivity extends AppCompatActivity {
int iProgress = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button button = findViewById(R.id.button);
final CircularProgressIndicator progress = findViewById(R.id.cpi);
Handler handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
while (true ) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
progress.setProgress(iProgress,true);
}
},100 );
try {
Thread.sleep(100);
} catch (InterruptedException e ) {
e.printStackTrace();
}
}
}
}).start();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v ) {
iProgress = 0;
//progress.setVisibility( CircularProgressIndicator.VISIBLE );
button.setText("Calculating...");
for( int k = 0; k < 192; k++ ) {
Thread.yield();
for( int fk = 1; fk <= 1000; fk *= 10 ) {
for( int j = 0; j < 192; j++ ) {
for( int fj = 1; fj <= 1000; fj *= 10 ) {
for( int i = 0; i < 192; i++ ) {
for( int fi = 1; fi <= 1000; fi *= 10 ) {
// Calculate E192 three resistor resistive divider here, not relevant for the test
iProgress = k;
}
}
}
}
}
}
button.setText("Start");
//progress.setVisibility( CircularProgressIndicator.GONE );
}
});
}
}
Also the text on the button does not change, I expect that to be the same problem.
You are running the calculation on the main UI thread which causes the "stalled" UI behavior. Button onClick
handlers are executed on the UI thread. Remember there is only one UI thread - so if it is busy doing calculations it can't refresh display.
Here are some improvements:
(1) First the UI progress update can be simplified to a self-arming runnable (which runs on the UI thread) to update the progress every 100MS. NOTE: no sleeps. And self-terminates when progress reaches 100. It is started when the button is pressed. It also updates the button with some feedback.
Button b = findViewById(R.id.button);
// The "new Handler()" is deprecated.
Handler handler = new Handler(Looper.getMainLooper());
Runnable r = new Runnable() {
@Override
public void run() {
progress.setProgress(iProgress);
if (iProgress < 100) {
handler.postDelayed(this, 100);
b.setText(String.format("RUNNING: %d", iProgress));
}
}
};
// The handler progress updater is started when button is pressed (below)
(2) Use button processing to start a new thread (not on the UI thread by definition) to perform the calculation, update the progress and update the button as needed.
b.setText("Start");
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
b.setText("Running");
b.setEnabled(false);
iProgress = 0;
// start the progress updater
handler.postDelayed(r, 100);
new Thread(new Runnable() {
@Override
public void run() {
long d = System.currentTimeMillis();
for (int k = 0; k <= 100; k++) {
for (int j = 1; j < 100_000_000; j++) {
// some calculation
double x = (double) d / j;
}
iProgress = k;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
b.setText("Done - Restart");
b.setEnabled(true);
}
});
}
}).start();
}
});
And result: