Android: Update a TableLayout in background - java

Working on an Android application, and I need a TableLayout to update on a regular basis without clogging the UI thread. Here is the TableLayout:
<TableLayout
android:id="#+id/pidata"
android:layout_width="wrap_content"
android:layout_height="match_parent">
</TableLayout>
Here is the gist of the code that needs to run, and update the TableLayout with new data every couple of seconds.
public static TableLayout piTableLayout;
public static HashSet<PIModel> PiData;
piTableLayout = (TableLayout)findViewById(R.id.pidata);
while (isRunning) {
PiData = getPiData();
piTableLayout.removeAllViews();
for (PIModel model : PiData) {
TableRow row = new TableRow(this);
TableRow.LayoutParams rowParams = new TableRow.LayoutParams();
rowParams.setMargins(0, -4, 0, -4);
rowParams.height = 40;
TextView pointNameText = new TextView(this);
pointNameText.setText(model.getAttributeName());
pointNameText.setGravity(Gravity.LEFT);
pointNameText.setLayoutParams(rowParams);
pointNameText.setTextColor(Color.parseColor("white"));
pointNameText.setTypeface(null, Typeface.BOLD);
pointNameText.setPadding(0, 0, 10, 0);
TextView valueText = new TextView(this);
valueText.setText(model.getValue());
valueText.setLayoutParams(rowParams);
valueText.setTextColor(Color.parseColor("white"));
valueText.setPadding(0, 0, 10, 0);
piTableLayout.addView(row);
row.addView(pointNameText);
row.addView(valueText);
}
try {
Thread.sleep(3000);
}
catch (InterruptedException ex) {
}
}
Previous Code
Handler piHandler = new Handler();
Runnable piRunnable = new Runnable() {
#Override
public void run() {
while (isRunning) {
piHandler.post(new Runnable() {
#Override
public void run() {
//logic to add to TableLayout
}
});
try {
Thread.sleep(3000);
}
catch (InterruptedException ex) {
}
}
}
};
new Thread(piRunnable).start();

You can't call Thread.sleep on the UI Thread, cause it will block all updates of the screen while the method doesn't finish, you must either use one of the following solutions:
Use android.os.Handler with repeting messages (handleMessage) that will run on UI Thread but will wait on background for new messages to arrive; (Simply solution)
Use a Thread by yourself and post messages to a Handler to go back to UI Thread; (Best solution)
Use a CountdownTimer (that does need an fixed ammount of time to start and reachs 0, stoping the proccess);
Use AsyncTask with an while(true) loop and use publishProgress from doInBackground and handle the state change at onProgressUpdated to touch the UI (Not recomended, cause AsyncTasks must have short lifes);
Use a Service to do the same as the AsyncTask and send Broadcasts to the Activity; (Complex solution)

Related

Trying to make a basic counter using scheduled executor service

I'm new to java and was trying to make a simple android app that does the following:
display a window in the main activity
every second the value of integer i is increased by 1
update the window content to display the value of i every second
for example the window should display the following text:
1st second: "update 1"
2nd second: "update 2"
3rd second: "update 3"
...
etc
I learned that using java's ScheduledExecutorService with ScheduleAtFixedRate is better than implementing an infinite loop, so I attempted the following code:
public class MainActivity extends AppCompatActivity {
public FrameLayout mLayout;
public WindowManager wm;
public WindowManager.LayoutParams lp;
public int i;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// display a window saying "hello"
wm = (WindowManager) getSystemService(WINDOW_SERVICE);
mLayout = new FrameLayout(this);
lp = new WindowManager.LayoutParams();
lp.format = PixelFormat.TRANSLUCENT;
lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(R.layout.text_bubble, mLayout);
((TextView) mLayout.findViewById(R.id.text)).setText("hello");
wm.addView(mLayout, lp);
//creating a scheduled executor service, it runs the method "myTask" at fixed rate of 1 second
final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
myTask();
}
}, 0, 1, TimeUnit.SECONDS);
}
private void myTask() {
Log.v(TAG,"Running");
//Increment i by 1 and update the window layout
i = i + 1;
((TextView) mLayout.findViewById(R.id.text)).setText("update " + i);
wm.updateViewLayout(mLayout, lp);
}
}
When I start the app it does display the window as intended, but it doesn't update the number every second, instead it only shows "update 1". I can't tell why as I didn't get any errors. After some trial and error I found that when I remove the last two lines from the myTask method (the lines responsible for updating the text of the window layout):
((TextView) mLayout.findViewById(R.id.text)).setText("update " + i);
wm.updateViewLayout(mLayout, lp);
when I remove these two lines the executor service will function just fine, and I can see that by watching the logged text message "running" popping every second. but when I add those two lines again, it doesn't pop these messages anymore(except for the first second).
so what am I doing wrong here? I thought the problem could be about the the way I update the layout maybe. what am I missing here?
Try this:
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
//Your stuff
}
}, 0, 1000);
Or this using the scheuldedServiceExecutor:
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
//Your stuff
}, 1, 5, TimeUnit.SECONDS);

Why variable gets reset on button click(s) in Android?

Can someone help me to understand what is happening here? Have been trying to debug, but feel like stuck!
I am trying to animate some online images in my Android app using the following method.
private void animateImages() {
// URL loading
// int i = 1; (initialized earlier)
// ArrayList<String> myImages = new ArrayList<>(); (initialized earlier)
myImages.clear();
While (i < 11) {
// Adds ten images using web link
myImages.add("My_web_url");
i++;
}
AccelerateInterpolator adi = new AccelerateInterpolator();
try {
Field mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
mScroller.set(viewPager, new MyScroller(getApplicationContext(), adi, 1));
}
catch (NoSuchFieldException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
if (viewPager != null) {
viewPager.setAdapter(new MyPageAdapter(getApplicationContext(), myImages));
}
final Handler handler = new Handler();
final Runnable Update = new Runnable() {
// Printing variables for debugging
System.out.println("The page number is=" + currentPage);
System.out.println("The myImages size is=" + myImages.size());
public void run() {
if (currentPage == myImages.size() - 1) {
currentPage = 0;
}
viewPager.setCurrentItem(currentPage++, true);
}
};
timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(Update);
}
// delay and period can be initialized as desired
}, delay, period);
}
}
When I call this method in OnCreate, animation works fine. However, when I call this method in OnClickButton Listener, variable myImages size (before public void run()) become zero and due to this animation doesn't work.
In the above, MySCroller and MyPageAdapeter are java classes. But, most likely, the issue is related to button click, and I don't understand why it resets the myImages size which halts the animation!
This is how button click listener is called. What am I doing wrong?
MyButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
animateImages();
}
});
Edit 1:
Thanks to all the comments, I made a little progress.
I moved all these variables from MainActivity to animateImages() method. The animation runs with button click as well but there is a bump in animation, where too images moves too fast then bump and so on..
// Added just before while loop
DELAY_MS = 1000;
PERIOD_MS = 1000;
i = 1;
currentPage = 0;
I notice the same animation bump if I move the URL loading while loop to OnCreate().
The second time you call animateImages it clears myImages but then doesn't loop because i is not reset so it remains empty. Move creation of that list to onCreate instead to avoid that issue.

Update a textView in real time (using a for)

So I have an android app where I want to decrement a value and display it in a textview. I start from 1000 and decrement it by 1 from 1 to 1 seconds. This acts as a score that decreases in time if you stay more on the level. This is my code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
timeText = (TextView) findViewById(R.id.textView5);
runOnUiThread(new Runnable() {
#Override
public void run() {
for(time=1000;time>=0;time--){
try {
TimeUnit.SECONDS.sleep(1);
timeText.setText(String.valueOf(time));
System.out.println(time);
}
catch(Exception e)
{
System.out.println("Error on delay");
}
}}
});
}
My error is that whenever I enter this activity, the screen turns black. The console is printing the values from second to second and if i comment the "for" the textView displays properly the value 1000 (if i declare int time = 1000 of course). I am really not sure what the problem is here. Does somebody know what i'm doing wrong?
You can't just loop on the UI thread like that. Inside Android there's a message loop on the UI thread. When it needs to draw, it sends a message to that message loop. Until you process that message the changes won't appear on screen. And to process a message, your code must finish and return to the message loop.
If you want to do this, you can't use a for loop on the UI thread. You need to send individual messages to a Handler for each draw you want to make.
You are pausing the UI in that for loop.
To achieve what you want, either use a Handler
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//update textview here
}
},1000);
OR
use a Timer
new Timer().scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
//update
}
},1000,0);
I don't know much about handlers but you don't need a for loop in timer.
In your case:
long time=1000;
new Timer().scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable(){
timeText.setText(String.valueOf(time))
time--;
});
}
},1000,0).start();
Good luck

Editing TextView outside the UI Thread

I have the below code:
new Thread(new Runnable(){
public void run(){
try {
pos = Calculo.Calcular();
mostrarFrases(pos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
where mostrarFrases is:
void mostrarFrases(int pos){
Mostrar = (TextView) findViewById(R.id.Texto);
Mostrar.setText(Frases[pos*2+1], 0, Frases[pos*2+1].length);
}
It works if i haven't any thread but without it does not work. I need thread because i need to wait until Calculo.Calcular() finish its work.
Rule: You cannot manipulate UI elements outside the UI thread.
Here is the proper way to do things:
//Asuming that your activiy is named MainActivity
new Thread(new Runnable() {
public void run() {
try {
pos = Calculo.Calcular();
//Manipulate your UI elements as following
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
mostrarFrases(pos);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
You're not allowed to touch Views on any thread other than the UI thread.
To solve this, try the following:
void mostrarFrases(final int pos){
runOnUiThread(new Runnable() {
public void run() {
Mostrar = (TextView) findViewById(R.id.Texto);
Mostrar.setText(Frases[pos*2+1], 0, Frases[pos*2+1].length);
}});
}
You should not manipulate UI elements on any thread except the main UI thread.
So, in your case you got two choices: either use runOnUIThread(Runnable), or use AsyncTask and do UI manipulations in onPostExecute().
I'd go with AsyncTask - it is intended for this kind of scenarios.
UI manipulation must be done in main/ui thread. You have two options for this case:
You have a reference to your Activity object.
myActivity.runOnUiThread(...);
Bind a handler to the main thread and:
// Main thread
Handler handler = new Handler();
// Another thread
handler.post(new Runnable(){...});

Updating Multiple Textbox Java GUI

I have an activity or form in which there is one text box called time here. As suggested by experts from this forum I am using runnable to update the TextBox while receiving the data from wifi.
My doubt is what to do when I want to update multiple TextBox's. Should I use multiple blocks of runnables like
time1.post(new Runnable() {
#Override
public void run() {
time2.setText(s1);
}
});
time2.post(new Runnable() {
#Override
public void run() {
time2.setText(s2);
}
});
time3.post(new Runnable() {
#Override
public void run() {
time3.setText(s2);
}
});
Or some other technique is there to update multiple TextBoxes? My present code is like below.
package com.example.cdttiming;
public class MainActivity extends Activity
{
EditText time;
String s;
Button button;
byte[] buffer = new byte[65535];
InetAddress ia = null;
byte[] bmessage = new byte[1500];
DatagramPacket dp = new DatagramPacket(bmessage, bmessage.length);
MulticastSocket ms = null;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
time = (EditText) findViewById(R.id.et_time);
try
{
WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
WifiManager.MulticastLock multicastLock = wm.createMulticastLock("multicastLock");
multicastLock.setReferenceCounted(true);
multicastLock.acquire();
ia = InetAddress.getByName("226.1.1.1");
try {
ms = new MulticastSocket(4321);
} catch (IOException e) {
e.printStackTrace();
}
try {
ms.joinGroup(ia);
} catch (IOException e) {
e.printStackTrace();
}
ms.setReuseAddress(true);
}
catch (UnknownHostException e) {
time.setText(e.getMessage());
}
catch (IOException e) {
time.setText(e.getMessage());
}
}
public void startProgress(View view) {
Runnable runnable = new Runnable() {
#Override
public void run() {
while(true) {
try {
// String str="This is test string";
ms.receive(dp);
s = new String(dp.getData(),0,dp.getLength());
char retval[] = s.toCharArray();
}
catch (UnknownHostException e) {
time.setText(e.getMessage());
}
catch (IOException e) {
time.setText(e.getMessage());
}
****////// My doubt is here if i have multple strings of data and multiple
/// multiple textboxes to update then what to do ???****
time.post(new Runnable() {
#Override
public void run() {
time.setText(s);
}
});
} // while
}
};
new Thread(runnable).start();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Your code is a bit confusing. In one case inside your primary while loop you are capturing the data, assigning it to a String variable s then using the text widget post() function and a Runnable to set the EditText widget to that value. But inside that same while loop you have exception handlers that simply set the same EditText widget directly. Your code also looks like you could potentially lose messages if the while loop resets the value of s before the timer loop has a chance to fire the set text call.
It appears you are trying to create some form of real-time system and need the primary while loop to continually process, and display data as it becomes available. Now you have 3 different consumers (text widgets), but you didn't mention if you also have 3 different sources of messages or is there still only one main processing loop and some form of selector will decide which text widget gets the message?
Were I building something along these lines, I would probably use a messaging system and follow the producer-consumer model. When text was received, I would have the primary processing loop push a simple 2-field message onto a queue that contained a reference to the text widget and a reference to the data string. Because Strings are immutable in Java, once the message object had its own copy of the text, any changes to s would not affect the message.
Then, I would have a second thread running in the background that consumes the message queue. It would pull the message off the queue, construct a post call to the target text widget with the message data, fire it off, then go back to get the next message.
By going this route you separate the data processing thread from the UI update processing and would not need to worry about how many text widgets or other widgets you need updated. If you ever need to add others you only need to worry about the new widgets being known to the code that creates the update messages. The thread doing the widget update doesn't know how many widgets you have, it simply uses the one referenced in the update message object that the message creator said to use.
I suggest you only create one runnable and post it once on main thread like this :
time1.post(new Runnable() {
#Override
public void run() {
time2.setText(s1);
time2.setText(s2);
time3.setText(s3);
}
});
The need to create a runnable and to post it on the main thread handler of a view is only about running a piece of code on the UI thread. No matter where you get the main thread handler reference from.
you could also have created your how handler on main thread :
protected void onCreate(Bundle savedInstanceState)
{
this.uiThrdHandler = new Handler();
}
then post a runnable using it :
this.uiThrdHandler.post(new Runnable(){
...
});
Of course there is no need to create another handler but it's for demonstration purpose.
The Activity object has an utility method for that purpose : runOnUiThread
Using it, it would be :
MainActivity.this.runOnUiThread (new Runnable() {
#Override
public void run() {
time2.setText(s1);
time2.setText(s2);
time3.setText(s3);
}
});
But again, the result is the same.

Categories