I'm trying to show feedback the percentage of which they have downloaded data from a server.
The flow goes something like this:
LoginActivity
- onSuccess, call Service to download data
- while Service is working, I've already switched the user to HomeActivity
HomeActivity
-This activities layout holds a Fragment I created called LoadingFragment
-There is a method in LoadingFragment that is subscribed to an event that is posted by the Service
My problem is that the Fragment UI is not changing at all whenever an event has been posted.
I logged everything, and I can see the event posting the correct data but the data is not being reflected on the UI.
Below is my Service:
#Override
public int onStartCommand(Intent intent, int flags, final int startId) {
Integer id = intent.getIntExtra("id", 0);
//do sync here
Log.i(LOG_TAG, "Make rest call to retrieve all r for id: " + id);
mRestClient.getApiService().getR(id, new Callback<List<R>>() {
#Override
public void success(List<R> rResponse, Response response) {
Integer outstanding = routeResponse.size();
Integer percentage;
LoadingEvent loadingEvent = new LoadingEvent();
SyncEvent syncEvent = new SyncEvent();
Log.i(LOG_TAG, "Callback success r");
System.out.println("yohellotest");
Log.i(LOG_TAG, "Begin insertion");
DaoMaster daoMaster = MyApp.getDaoMaster();
DaoSession session = daoMaster.newSession();
SQLiteDatabase db = session.getDatabase();
RDao rDao = session.getRDao();
WDao wDao = session.getWDao();
JDao jDao = session.getJDao();
Log.i(LOG_TAG, "--Beginning Transaction--");
//db.beginTransaction();
try {
int counter = 0;
for(int i = 0; i < rResponse.size(); i++) {
R r = rResponse.get(i);
rDao.insert(r);
Log.i(LOG_TAG, "inserted r: " + r.getId());
for(int j = 0; j < r.getW().size(); j++) {
W w = r.getW().get(j);
wDao.insert(w);
Log.i(LOG_TAG, "inserted w: " + w.getId());
for(int k = 0; k < w.getJ().size(); k++) {
J j = w.getJ().get(k);
jDao.insert(j);
Log.i(LOG_TAG, "inserted j: " + j.getJId());
}
}
counter += 1;
percentage = (int) Math.floor((double) counter / outstanding * 100);
loadingEvent.setPercentage(percentage);
bus.post(loadingEvent);
}
Log.i(LOG_TAG, "Finished inserting");
//db.setTransactionSuccessful();
} catch (Exception e) {
Log.i(LOG_TAG, "Exception happened " + e.getMessage().toString());
System.out.println("yo something happened, but I don't know what");
} finally {
Log.i(LOG_TAG, "--Closing transaction--");
//db.endTransaction();
}
syncEvent.setIsSuccess(true);
syncEvent.setStatus(200);
bus.post(syncEvent);
}
#Override
public void failure(RetrofitError error) {
Log.i(LOG_TAG, "Rest call failed for this api");
System.out.println("failtests");
}
});
return START_STICKY;
}
Fragment Subscribed Method
#Subscribe
public void loadingResponse(final LoadingEvent loadingEvent) {
Log.i(LOG_TAG, "Response from loading: " + loadingEvent.getPercentage());
if(getActivity() == null)
return;
textTest.setText(loadingEvent.getPercentage().toString());
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Log.i(LOG_TAG, "Updating ui for loading text" + loadingEvent.getPercentage().toString());
mCircularProgressBar.setProgress(loadingEvent.getPercentage());
textTest.setText(loadingEvent.getPercentage().toString());
}
});
}
When I check my Logs, I see everything coming in fine, but the UI is not reflecting it. What am I missing here?
EDIT
Forgot to mention. I'm using this library for my circle progress bar:
CircleProgressBar
EDIT
As of today, I still have yet to figure this out. I also noticed that my adapter is not being updated when the service posts the event. Previously, the context used to startService() was the getApplicationContext() from the LoginActivity. I thought this might have been a problem so I changed it to get the instance of the HomeActivity instead. I thought that firing the service from my HomeActivity would have solved the problem, but I'm still getting the same results unfortunately.
Okay so no one really tried to attempt answering my question but I figured it out.
I actually skimped over the documentation part of the Service class and didn't know that it was executing on the Main Thread. To fix this, I had to run an AsyncTask to execute my operation in the background.
The only thing I had to change in my codebase was to move the executing code from the onStartCommand method into an AsyncTask. The AsyncTask will be initiated onStartCommand and everything should be working fine.
My code looks something like this now :
Service
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
DownloadTask downloadTask = new DownloadTask();
downloadTask.execute();
return START_STICKY;
}
private class DownloadTask extends AsyncTask<Void, Void, LoginEvent> {
#Override
protected LoginEvent doInBackground(Void... params) {
//Code here to run my operations
//Call api, insert to db, post events etc...
//
}
#Override
protected void onPostExecute(LoginEvent event) {
//run anything here you want to do after execution
}
}
You can ignore my LoginEvent as this was something for my event bus to post events to any subscribers.
Also, keep in mind that if you are going to change any views from this AsyncTask through the bus, make sure the subscriber uses the runOnUi to execute or else you'll hit a runtime exception since you're trying to change a view from something other than the MainThread.
Related
i want to implement a login activity. it checks user existance with a webservice.
EditText un=(EditText) findViewById(R.id.txtName);
EditText pass=(EditText) findViewById(R.id.txtPass);
WS ss=new WS();
String str=ss.execute("checkLogin",un.getText().toString(),pass.getText().toString()).get();
Intent in=new Intent(arg0.getContext(), Overview.class);
in.putExtra("username", str);
if(str=="No User")
Toast.makeText(getApplicationContext(), "ss", 300).show();
else
{
startActivity(in);
finish();
}
the problem is in "IF" section. "str" value sets after finishing code lines.
but i want to get "str" value then check for IF to run proper actions.
You need override onPostExecute method see below example
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
The whole point of an asynchronous task is to not block synchronously. You have several options if you do want synchronous behavior:
Don't use AsyncTask in the first place (probably not a good idea if it is a long-running network call)
Put the code you want to run after the AsyncTask completes it's background work in the onPostExecute method, which will have access to the result of the data returned from the background method
Provide a callback to the AsyncTask that onPostExecute can call when it is finished (similar in concept to the previous option but more formal and robust to changes)
I prefer the last option, but it also takes more dev time to write than the other options.
Don't forget that an AsyncTask may do it's background work and/or finish after the activity has been finished and shut down, so make sure to check for that state appropriately before you start interacting with the UI.
If you are trying to do a network call you shouldn't use asynctask rather use loopj or google's volley library .
Asynctask is not meant for long network calls , having said that here's an example of asynctask class , code:
class AsyncTaskExample extends AsyncTask<Void, Integer, String> {
private final String TAG = AsyncTaskExample.class.getName();
protected void onPreExecute(){
Log.d(TAG, "On preExceute...");
}
protected String doInBackground(Void...arg0) {
Log.d(TAG, "On doInBackground...");
for(int i = 0; i<5; i++){
Integer in = new Integer(i);
publishProgress(i);
}
return "You are at PostExecute";}
protected void onProgressUpdate(Integer...a){
Log.d(TAG,"You are in progress update ... " + a[0]);
}
protected void onPostExecute(String result) {
Log.d(TAG,result);
}
}
Edit it as you wish and instanciate a new one in your code when you want to do the check up ,
Hope it helps
What i'm trying to do should be pretty simple, i'm calling an IntentService to check periodically in background if the current time is before or after a specified time (passed as Extras in the intent from the MainActivity) and get a notification when it happens.
In Thread's run() method i get an instance of the current time and use it as comparison to the previously specified one in an if/else construct, before re-running the Thread.
The problem is that it always go for "else" even if the current time is after the other one. This problem is just too strange, can anyone explain me where is the error?
Here is my IntentService class:
public class MyCheck extends IntentService {
int hour = 0; int minute = 0;
int i = 0;
private static final String TAG = "Check";
int hourNow;
int minuteNow;
Handler handler = new Handler();
public MyCheck() {
super(MyCheck.class.getName());
}
#Override
protected void onHandleIntent(Intent intent) {
Log.w(TAG, "Service Started!");
hour = intent.getIntExtra("hour", 0);
minute = intent.getIntExtra("minute", 0);
Log.w(TAG, "Step 1 ");
Thread runnable = new Thread() {
#Override
public void run() {
/* do what you need to do */
Log.w(TAG, "Step 2 ");
/* get current time */
Calendar c = Calendar.getInstance();
hourNow = c.get(Calendar.HOUR);
minuteNow = c.get(Calendar.MINUTE);
//HERE, IT SEEMS THAT IT'S ALWAYS BEFORE hour AND minute
if(hourNow > hour || (hourNow == hour && minuteNow > minute)) {
//SEND
Log.w(TAG, "RUN ");
Intent broadcast = new Intent();
broadcast.setAction(NOTIFICATION_SERVICE);
sendBroadcast(broadcast);
handler.removeCallbacks(this);
}
else {
//NOTHING
i++;
Log.w(TAG, "NOT YET " + i);
}
//REPEAT EVERY 6 SECONDS
try {
sleep(6000);
handler.post(this);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
runnable.start();
}
}
EDIT
I've found the trivial problem as you can see in my answer below.
If someone wants to complete the answer with some more information about the problems that can occur during the building process they will be apreciated. TIA
The simple solution to this was to wait and reboot Windows. The code wasn't flawed after all, it's just something on the building process that needed some time to tell the compiler that the error wasn't there: maybe there was a copy in cache of a past error that was still taken in account, i'm not sure.
Maybe you can add some information to my last statement, posting an answer or a commenting about it.
In this case, I have 2 activities. I'm on Activity 1 and go to Activity 2. The application works as intended.
The problem starts when I go back to Activity 1, and start Activity 2 again.
See code below:
public class ScreenWActivity extends SerialComActivity {
private static final String tag = "ScreenWActivity";
private TextView mReception, m_tvDate, mtvPesoPercent, mtvState;
public String mCommand = null;
public int mActualProcess, mNextProcess;
private Commands mLastCommand;
public SettingsGlobal mSettings;
public int mAttempts = 0;
public long mStartTime, mTimeout;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_W);
this.mSettings = new SettingsGlobal(this); // get global settings
this.mtvState = (TextView) findViewById(R.id.tv_state); // label to update the current state
startSerialConnection(); // open serial port and start connection. inherited from SerialComActivity (the upper class)
this.mTimeout = 10; // timeout for commands response in seconds
this.mNextProcess = 1; // the next step in the process, its updated in the stepN() methods
this.mActualProcess = 1; // current step in the processo
this.mLastCommand = Commands.OPEN_DOOR; // the last command I've sent, to know what to expect in return
this.executeWorkflow(mNextProcess); // starts the workflow
}
private void step1(){
this.mtvState.setText("Closing door."); // update status
this.writeSerial(Commands.OPEN_DOOR.command("080").getBytes()); // sends the command to the outputstream, the external device reads the command, execute it and respond back
this.mNextProcess = 2; // the next step in the process is 2
this.mActualProcess = 1; // just tracking
this.mLastCommand = Commands.OPEN_DOOR;
startCounting(); // starts the timout, I've sent the command, now I wait for an answer
}
private void step2(){
this.mtvState.setText("Testando peso das balanças 1.");
this.writeSerial(Commands.GET_W.command().getBytes()); // get weight from weighing-machine
mLastCommand = Commands.GET_W; // the last command i sent i requested the weight - now I know what to expect
mNextProcess = 3; // next step in the sequence in case everything goes according to plan
this.mActualProcess = 2; // tracking
startCounting(); // starting timeout to get an answer
}
private void step3(){...}
private void step4(){...}
private void step5(){...}
private void step6(){...}
#Override
protected void writeSerial(byte[] buffer){...}
public void startCounting(){
mStartTime = System.currentTimeMillis();
timerHandler.postDelayed(timerRunnable, 0);
}
public void stopCounting(){
timerHandler.removeCallbacks(timerRunnable);
}
#Override
protected void onDestroy() {
// TODO Auto-generated method stub
stopCounting();
timerRunnable = null;
if(this.mSerialPort != null)
this.mSerialPort.close();
this.mSerialPort = null;
if(AppConfig.DEBUG) Log.i(tag, "finishing!");
finish();
super.onDestroy();
}
public void executeWorkflow(int step) {
switch(step){
case 1:
step1();
break;
case 2:
step2();
break;
case 3:
step3();
break;
case 4:
step4();
break;
case 5:
step5();
break;
case 6:
step6();
break;
}
}
protected boolean validateReturn(String resposta) {
/// we check the command we've sent and the response we're given. if it matches, then we return true, else false
}
// overrided from SerialComActivity, called when the external equipment sends a message to us
// ITS CALLED WHEN THERE IS INPUT FROM THE EXTERNAL EQUIPMENT
#Override
protected void onDataReceived(final byte[] buffer, final int size) {
runOnUiThread(new Runnable() {
public void run() {
stopCounting(); // we remove the callbacks from the timeout thread
if( validateReturn(new String(buffer, 0, size).trim()) ){ // we check if the response is good
executeWorkflow(mNextProcess); // if its good, we move to the next step
}else{
mtvState.setText("invalid return"); // if not we message the user
executeWorkflow(mActualProcess); // we try again
}
}
});
}
// RESPONSIBLE FOR THE TIMEOUT
// the code below was created intending to implement a timeout timer for waiting a response from the external device
Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
#Override
public void run() {
long millis = System.currentTimeMillis() - mStartTime;
long seconds = (millis / 1000);
timerHandler.postDelayed(this, 500);
if(mTimeout - seconds == 0 ){
mAttempts += 1;
if(mAttempts == 3){ // we make 3 attempts to get a response, if it is the third, we quit trying and give error
mAttempts = 0;
mtvState.setText("Could not communicate.");
stopCounting(); // we end the timer
}else{
executeWorkflow(mActualProcess); // if we can still try, we send the command again
}
}
}
};
}
Inside the method onDataReceived(), which is called everytime I get a response from the external equipment, I use the attribute mLastCommand (which indicates the last command I've sent), so this way I know how to validate the response I get.
When I go back to Activity 2, in the class scope the values of the attributes are the same as the ones I've defined in the onCreate() method. In the LogCat I saw that the attributes values are correctly defined as stated in OnCreate.
BUT, when the method onDataReceived (it's inside a Thread in the SerialComActivity class) is called (which is called when I get data from outside) the value of this same attribute mLastCommand is the same as the first time I started the activity, regardless of the value I define for it. As if the the runnable inside RunOnUiThread is still holding the old values from the first time I entered the activity, and outside of it the class has the values I have defined.
It's like having two different attributes with the same name in the ScreenWActivity.
I tried nulling the attributes inside the onDestroy() method, but to no avail.
Below is the code for the SerialComActivity class:
public abstract class SerialComActivity extends Activity{
SerialPort mSerialPort = null;
protected OutputStream mOutputStream;
protected InputStream mInputStream;
protected ReadThread mReadThread;
private class ReadThread extends Thread {
#Override
public void run() {
super.run();
while(!isInterrupted()) {
int size;
try {
byte[] buffer = new byte[64];
if (mInputStream == null) return;
size = mInputStream.read(buffer);
if (size > 0) {
onDataReceived(buffer, size);
}
} catch (IOException e) {
e.printStackTrace();
return;
}
}
}
}
protected void startSerialConnection(){
try {
mSerialPort = new SerialPort(new File("/dev/ttyS2"), 38400, 0);
} catch (SecurityException e) {
// TODO Auto-generated catch block
if(AppConfig.DEBUG)
Log.e("SERIAL", "portopen ERR: " + e.getMessage());
} catch (IOException e) {
// TODO Auto-generated catch block
if(AppConfig.DEBUG)
Log.e("SERIAL", "portopen ERR: " + e.getMessage());
}
mOutputStream = mSerialPort.getOutputStream();
mInputStream = mSerialPort.getInputStream();
/* Create a receiving thread */
mReadThread = new ReadThread();
mReadThread.start();
}
protected abstract void onDataReceived(final byte[] buffer, final int size);
protected abstract void writeSerial(byte[] buffer);
#Override
protected void onDestroy() {
if (mReadThread != null){
mReadThread.interrupt();
if(AppConfig.DEBUG) Log.i("ThreadSerial", "interrupting");
}
if(mSerialPort != null)
mSerialPort.close();
mSerialPort = null;
finish();
super.onDestroy();
}
}
I'm still in the process of learning Java and Android programming, so please forgive me if I'm doing something wrong. I looked up around, and the thing that you can't use variables other than "final" inside the RunOnUiThred came up. But I think it's not the issue, since it works the first time I start the activity.
Try doing your clean up in onPause() instead of onDestroy(), onDestroy() may not be called immediately which means there may be a read conflict on SerialPort. Also if you are already in onDestroy(), calling finish() doesn't really do anything.
Lastly, for a finite resource like SerialPort connection, it's better to put it in a Service.
I'm a newbie in Java, but I think I found out what was happening. The thing is that I asked the wrong question.
The problem is in the mInputStream.read(). As I've come to know, it's a blocking operation. I'm creating a thread that stays blocked in that read() method. After I finish the Activity, (go back to the first one), the thread keeps running. I know that because when a send some information through the serial interface, that thread responds.
So what I did, and it's working for me, altough many people stated that this method is not recommended is use mInputStream.available():
try {
if (mInputStream == null){ Log.i(tag,"returning"); return null ;}
Log.i(tag,"reading");
mEmptyStream = true;
while(mEmptyStream && !mFinish){
Log.i(tag,"input while");
/// checking if there is info, so we don't block the thread
if(mInputStream.available() > 0){
Log.i(tag,"input avail : " + InputStream.available());
//stream not empty
mEmptyStream = false;
size = mInputStream.read(buffer); //
}
}
if (size > 0) {
Log.i(tag,"size > 0 = " + new String(buffer, 0, size));
return new String(buffer,0,size);
}else{
Log.i(tag,"size <= 0");
}
}
Basically I loop using available(). When I finish the activity, in the onPause() method I set the field mFinish to true, this way the thread finds it's way out of execution and can end properly. It's the way I found and it's working so far. I improved the code significantly after the original post, like not running non UI jobs in the RunOnUiThread :)
But I tested it and it's working.
I have an application that uses IntentService to run a background task where I pull data from a website, parse the data out, and create calendar events based on the results. Everything seems to be working create, except I'm running into an issue with rotation.
Using the code below, when I rotate the screen, the ProgressDialog box stays visible, but is never updated with new text when the process is updated, and never goes away once the call is completed. I'm using an IntentService instead of an ASyncTask because the user can also schedule the IntentService to run at other times without having to interface with the app. Any help is appreciated, thanks!
public void onCreate(Bundle savedInstanceState) {
Object retained = getLastNonConfigurationInstance();
if (retained instanceof CalendarHandler) {
// CH is a class level variable defined at the top which references my IntentService, aptly named CalendarHandler
ch = (CalendarHandler) retained;
ch.setActivity(this);
} else {
ch = null;
}
activity = this;
btnLogin.setOnClickListener(OnClickListener(View view) {
ch = new CalendarHandler();
ch.setActivity(MyTlc.this);
// Do other stuff, like run the intent service
}
}
public Handler handler = new Handler() {
public void handleMessage(Message message) {
// We read the information from the message and do something with it
// based on what the result code is
String result = message.getData().getString("status");
if (result.equals("ERROR")) {
activity.removeDialog(PROGRESS_DIALOG);
results.setText(message.getData().getString("error"));
} else if (result.equals("DONE")) {
activity.removeDialog(PROGRESS_DIALOG);
int count = message.getData().getInt("count", 0);
activity.results.setText("Added " + count + " shifts to the calendar");
} else {
activity.pDialog.setMessage(result);
}
super.handleMessage(message);
}
};
From what I understand, this should work, and like I said the ProgressDialog box does stay properly, I just can't seem to pass information to the dialog box after rotating.
Here is my AsyncTask class:
private class Task extends AsyncTask<Void, Integer, Void> {
#Override protected void onPreExecute() {
dia = new ProgressDialog(pictures.this);
dia.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dia.setMessage("Please wait while content is loaded...");
dia.setCancelable(false);
dia.show();
}
#Override protected Void doInBackground(Void... voids) {
Thread thread = new Thread(null, uploader, "MagentoBackground");
thread.start();
//publishProgress(100); keep this commented or no?
return null;
}
#Override public void onProgressUpdate(Integer... prog) {
if (prog == null)
return;
dia.setProgress(prog[0]);
}
#Override protected void onPostExecute(Void voids) {
dia.setMessage("Done");
dia.cancel();
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
There is one major and one minor problem I am concerned with right now. Here are the major and minor problems, consecutively:
This app's intention is to take a picture and upload it to a website. As soon as the user hits the 'OK' button to upload, the AsyncTask dialog shows, so this is fine. However, it only shows the dialog box for a split second, already 100% completed, and then force closes. However, it is most certainly not 100% complete! When I look at the website and refresh the page, it doesn't show the picture. (On WiFi, the picture actually does upload near immediately, and when the website is refreshed right away, it shows the new picture. But on 3G, this process can take minutes - which is why I want the user to see the progress of the photo being uploaded).
I'm not sure how to use the onProgressUpdate. I've tried several tutorials, to no avail. If anyone can state what I can do for this situation, it will be helpful
Edit:
Runnable code:
private Runnable uploader = new Runnable() {
#Override
public void run() {
/**JER 3**/
if (teacherInfo != null)
teacher = " - " + teacherInfo ;
else teacher = "" ;
if (schoolInfo != null )
school = " - " + schoolInfo ;
else school = "" ;
/********/
if(Descriptor.desString.equals("")) Descriptor.desString = "No description provided.";
int sessionId = rapi.createSession(experimentInput.getText().toString(),
name.getText().toString() + teacher + school + " - " + time,
Descriptor.desString, "n/a", "Lowell, MA", "");
JSONArray dataJSON = new JSONArray();
JSONArray subData = new JSONArray();
try {
subData.put(curTime); subData.put(Lat); subData.put(Long); subData.put(Descriptor.desString);
dataJSON.put(subData);
}
catch (JSONException e) {
e.printStackTrace();
}
Boolean result = rapi.updateSessionData(sessionId, experimentInput.getText().toString(), dataJSON);
if (result) {
rapi.uploadPictureToSession(picture, experimentInput.getText().toString(),
sessionId, name.getText().toString() + teacher + school + " - " + time,
name.getText().toString() + Descriptor.desString);
}
//if (m_ProgressDialog != null && m_ProgressDialog.isShowing()) m_ProgressDialog.dismiss();
}
};
You are starting a new thread inside an already seperated thread.
doInBackground() gets executed in a different thread then the UI-methods onPreExecute(), onPostExecute() and onProgressUpdate() of the AsyncTask class. All that your async task does here is start a third thread. This takes almost no time. After that your task is finished, resulting in the dialog closing immediately.
You can do your uploading inside doInBackground(), you don't need to start a new thread there. That's what the AsyncTask class does for you already.
Regarding progress: onProgressUpdate() gets called every time you execute publishProgress(). So this is correctly implemented, you just have to call publishProgress() multiple times during the upload process to update the progressbar accordingly (e.g. every percent sent).