I have an app connected to a Java Servlet backend by means of an AsyncPost task. The task returns a String to the client representing a json object serialized with Gson.
It works almost fine, the problem is that I'm unable to access the Servlet response message from the class instantiating the call to the ServletPostAsyncTask: ListViewPrenota.class.
The project is structured as follows:
Both within the Servlet and the Client I created the two classes, Tour.class and Tours.class to store my data:
Tour class:
public class Tour {
// some simple int/string/list fields
}
Tours class:
public class Tours {
private List<Tour> tours;
// ...
}
On Client side, in a ServletPostAsyncTask.class, I receive the aforementioned Gson object within doInBackGround(). Within onPostExecute() I deserialize it, this way:
class ServletPostAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
private Context context;
Tours tours;
#Override
protected String doInBackground(Pair<Context, String>... params) {
//connect to Servlet and get the serialized Gson object
}
#Override
protected void onPostExecute(String jsonResponse) {
tours = (new Gson().fromJson(jsonResponse, Tours.class));
}
}
Now, from ListViewPrenota.class in Client I am calling the ServletPostAsyncTask:
ServletPostAsyncTask s = new ServletPostAsyncTask();
s.execute(new Pair<Context, String>(ListViewPrenota.this, "tours"));
Tours ttours = s.tours;
Tour tour = ttours.getTours().get(0);
Problem: I receive a java.lang.NullPointerException pointing to Tour tour = ttours.getTours().get(0);
What is the reasong preventing me to access the newly received Tours object from other classes than ServletPostAsyncTask?
Thank you very much
the problem is you are thinking that code runs serially, if you want to use stuff returned from the AsycTask you need to use it in onPostExecute or have a callback that sends the data after it is done
doInBackground(){
//do heavy work
}
onPostExecute(Data data){
//handle data
//send data via interface to activity or class that needs the data
//or just put everything that needs the data in here
}
Ok, it works. Here's what I was able to come up with:
Callback interface:
interface CallBack {
void callBackMethod(Tours tours);//do job
}
Caller class:
class ServletPostAsyncTask extends AsyncTask<Pair<Context, String>, Tours, String>{
private Context context;
Tours tours;
public ListViewPrenota listViewPrenota;
public ServletPostAsyncTask(ListViewPrenota listView){
this.listViewPrenota = listView;
}
#Override
protected String doInBackground(Pair<Context, String>... params) {
//communicate with Servlet and get a HttpResponse
}
#Override
protected void onPostExecute(String jsonResponse) {
tours = (new Gson().fromJson(jsonResponse, Tours.class));
//the callback starts a thread updating the UI in ListViewPrenota
listViewPrenota.callBackMethod(tours);
Toast.makeText(
context,
"Connected. \nTours size: "+ tours.getTours().size(),
Toast.LENGTH_LONG).show();
}
}
The callback interface's implementation within ListViewPrenota:
public class ListViewPrenota extends FragmentActivity implements CallBack{
private ProgressDialog m_ProgressDialog = null;
private Runnable viewOrders;
private TourAdapter m_adapter;
ListView listView;
private ArrayList<Tour> m_tours =null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view_prenota);
listView = (ListView) findViewById(R.id.list);
m_tours = new ArrayList<Tour>();
m_adapter = new TourAdapter(this, R.layout.list_row, m_tours);
listView.setAdapter(m_adapter);
getActionBar().setDisplayHomeAsUpEnabled(true); //pulsante drawer
getActionBar().setHomeButtonEnabled(true); //pulsante dietro
ServletPostAsyncTask spat = new ServletPostAsyncTask(ListViewPrenota.this);
String status = spat.getStatus().toString();
spat.execute(new Pair<Context, String>(ListViewPrenota.this,"tours"));
}
public void callBackMethod(final Tours tours){
System.out.println("I've been called back");
viewOrders = new Runnable(){
#Override
public void run() {
getOrders(tours);
}
};
Thread thread = new Thread(null, viewOrders, "MagentoBackground");
thread.start();
m_ProgressDialog = ProgressDialog.show(
ListViewPrenota.this,
"Please wait...",
"Retrieving data ...",
true);
}
public void getOrders(Tours tours){
try{
m_tours = new ArrayList<>();
m_tours.addAll(tours.getTours());
Thread.sleep(2000);
Log.i("ARRAY", "" + m_tours.size());
} catch (Exception e) {
Log.e("BACKGROUND_PROC", e.getMessage());
}
//add tours to the adapter
runOnUiThread(returnRes);
}
private Runnable returnRes = new Runnable() {
#Override
public void run() {
if(m_tours != null && m_tours.size() > 0){
m_adapter.notifyDataSetChanged();
for(int i=0;i<m_tours.size();i++)
m_adapter.add(m_tours.get(i));
}
m_ProgressDialog.dismiss();
m_adapter.notifyDataSetChanged();
}
};
If there's a better way to do it I accept further suggestions.
In the mean time, thank you very much
Related
I have the following code. My problem is, that I can't get the JSON.execute() to stop/cancel. I spend quite some time looking up possible answers but I wasn't able to find anything that really worked (e.g. JSON.cancel(true)). As soon as I turn the trackerswitch on, the AsnycTask starts running every 3 seconds just like it's supposed to. Is there a way to easily stop the AsyncTask from executing as soon as the trackerswitch is turned off?
private boolean tracking = false;
private Switch trackerswitch;
private final Timer timer= new Timer();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.table_layout);
final Handler handler=new Handler();
final int delay = 4000;
trackerswitch=findViewById(R.id.trackerswitch);
trackerswitch.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
NetworkAccess JSON = new NetworkAccess();
if(trackerswitch.isChecked()){
trackerswitch.setText("Tracking...");
tracking=true;
handler.postDelayed(new Runnable() {
#Override
public void run() {
NetworkAccess JSON = new NetworkAccess();
JSON.execute();
handler.postDelayed(this, delay);
}
},delay);
}
else{
tracking=false;
trackerswitch.setText("Start Tracking");
}
}
});
}
}
This is what's called in the network class:
public class NetworkAccess extends AsyncTask<Void, Void, Void> {
public ArrayList<String> alldata = new ArrayList<>();
public ArrayList<String> locationlist = new ArrayList<>();
int stride;
String data;
#Override
protected Void doInBackground(Void... voids) {//4B4ADC
SOME CODE WHICH ISN'T IMPORTANT FOR MY PROBLEM
alldata.addAll(elementlist);
locationlist.addAll(loctrack);
}
}
catch(IOException | JSONException e){
MainActivity.field1.setText(e.getClass().getCanonicalName());
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity.field1.setText(String.format("%20s %20s", alldata.get(0), alldata.get(1)));
COUPLE MORE OF THESE SETTEXT COMMANDS TO FILL A TABLE WITH DATA
}
}
Thanks for your help!
handler.postDelayed() adds objects of the Runnable you provide to the message queue, to be run at the specified interval. You need to remove all the queued objects from the message queue in order to cancel the execution. Calling JSON.cancel(true) does not affect other objects that are already added to the queue.
You'll have to retain a reference to your Runnable implementation and then call handler.removeCallbacks(r) to prevent further executions. Instead of using an anonymous class in handler.postDelayed().
This documentation page sheds more light on the matter.
Also refer this page for what happens when you call cancel(true) on an AsyncTask.
I searched similar questions like this but sadly I found them really confusing and also I'm still new on using Android and Retrofit.
I have a contact list JSON here
http://api.androidhive.info/contacts/
And already working List but then I wanted to handle the Retrofit process to another class so I can just call it whenever I want. I have the MainActivity calling for the UI and the RetrofitHandler which handles the success and failure method.
Here is my Main Activity
public class MainActivity extends AppCompatActivity{
//note i just simplifiend my code a little
private List<Contacts> contacts;
public String[] itemer;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RetrofitHandler retrofitHandler = new RetrofitHandler();
itemer = retrofitHandler.getContacts(this);
if (itemer != null) {
Toast.makeText(MainActivity.this,itemer[0],Toast.LENGTH_SHORT).show();
}
}
And here is my HandlerClass
ublic class RetrofitHandler {
public String[] item;
public static final String ROOT_URL = "http://api.androidhive.info";
public List<Contacts> contacts;
public String[] getContacts(final Context context) {
final ProgressDialog loading = ProgressDialog.show(context, "Fetching Data", "Please wait...", false, false);
RestAdapter adapter = new RestAdapter.Builder().setEndpoint(ROOT_URL).build();
ContactsAPI api = adapter.create(ContactsAPI.class);
api.getContacts(new Callback<Contacts>() {
#Override
public void success(Contacts contacts, Response response) {
loading.dismiss();
MainActivity update = new MainActivity();
List<Contact> contactList = contacts.getContacts();
item = new String[contactList.size()];
for (int i = 0; i < contactList.size(); i++) {
item[i] = contactList.get(i).getName();
}
}
#Override
public void failure(RetrofitError error) {
Toast.makeText(context, "Error Occured", Toast.LENGTH_LONG).show();
}
});
return item;
}
My problem is it runs smoothly with no error on LogCat. Unfortunately the Toast on the mainactivity won't appear.
Your issue is because of asynchronous nature of the request/response. Success or Failure callbacks fires upon response, which is happen in asynchronous manner. So basically your method returns before that happens, it means your item is null. You have to use synchronous mechanism or you have to check MVP pattern which gives perfect solution for your problem. See below is an example of using synchronous call (I've not tested it, but it should work).
public List<Contacts> getContacts(){
RestAdapter adapter = new RestAdapter.Builder().setEndpoint(ROOT_URL).build();
ContactsAPI api = adapter.create(ContactsAPI.class);
return api.getContacts();
}
See this tutorial to get more idea. I suggest you to move to retrofit 2 which is much more greater than retrofit 1.
So I'm just trying to create an Alert Dialog that is just a message (no buttons or titles). I want to display an alert dialog when a background task is running. The alert dialog will run on the UI thread.
Here's what I have done so far:
protected void onPreExecute() {
super.onPreExecute();
AlertDialog altDlg;
altDlg = new AlertDialog.Builder(AlertDialogActivity.this).create();
altDlg.setMessage("Retrieving Information. Please Wait");
altDlg.show();
}
I also tried doing this:
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setMessage("Retrieve Info. Please Wait").show();
The error I am getting with the first one is:
cannot find symbol 'AlertDialogActivity'
symbol: class AlertDialogActivity
location: class com.example.Device.Activity
The second attempt error says:
incompatible types: com.example.Device.Activity cannot be converted to android.content.Context
I'm not sure what I am doing wrong in either scenario. I just want to display a basic message when a background task is running and I was hoping the closest thing I can use is AlertDialog.
EDIT for how to set up AsyncTask properly:
Small background of what I want to do. I just want to read in a file, deserialize it and save it's contents to a db.
Right now I'm assuming I only need two activities.
One is my main activity:
public class MainActivity extends Activity {
/**
* Called when the activity is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.setup);
final Button setup_button = (Button) findViewById(R.id.setup_button);
setup_button.setOnClickListener (new View.OnClickListener() {
public void onClick(View view){
setContentView(R.layout.retrieve_info);
}
});
}
}
Now the onClick event just moves to the new view that is supposed to display the message or alert dialog that says retrieving information. Please Wait. It displays the message while reading a file and saving to db. Once the file is read and saved, The message should disappear and say something like setup complete.
My second activity so far is:
public class RetrieveInfoActivity extends AsyncTask<Void,Void,Void> {
private ProgressDialog progressBar;
private void retrieveInfo(String fileName) {
try {
File file = new File(fileName);
Scanner scanner = new Scanner(file);
//Read all the lines until there are no more lines
while (scanner.hasNextLine()) {
scanner.nextLine();
//TODO: deserialize and save to db
}
scanner.close();
}
catch (FileNotFoundException e) { e.printStackTrace(); }
}
#Override
protected Void doInBackground(Void... params) {
retrieveInfo("test.txt");
return null;
}
protected void onPreExecute() {
super.onPreExecute();
progressBar.setIndeterminate(true);
progressBar.setCancelable(false);
progressBar.setMessage("Retrieve Information.Please wait");
progressBar.show();
}
#Override
protected void onPostExecute() {
progressBar.dismiss();
}
}
That's all I really have so far. I just need to understand how to set up this in Android conceptually.
Hope this makes sense.
Try this:
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
Instead of using an AlertDialog use a ProgressBar, it will do the trick for you.
private ProgressDialog progressBar;
#Override
protected void onPreExecute() {
super.onPreExecute();
progressBar.setIndeterminate(true);
progressBar.setCancelable(false);
progressBar.setMessage("Your message");
progressBar.show();
}
#Override
protected void onPostExecute(final String error_code) {
progressBar.dismiss();
}
Looks like you are extending AsyncTask and trying to use it as a context. That won't work as AsyncTask itself is nothing but an abstract class.
You need to create a custom constructor for your AsyncTask to fetch the Context:
public class MyTask extends AsyncTask<Void, Void, Void> {
private Context mCtx;
public MyTask(Context context) {
mCtx = context;
}
...
Then when starting your AsyncTask, pass the context:
new MyTask(this).execute();
Another way would be to make the AsyncTask an inner class and use YourActivity.this when creating the dialog. Example:
public class YourActivity extends Activity {
...
private class MyTask extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
AlertDialog dialog = new AlertDialog.Builder(YourActivity.this).create();
}
#Override
protected Void doInBackground(Void... params) {
...
}
}
}
The question is how to communicate with an Android phone to a server, so that if the Activity is left and the call in the Activity was not successful to repeat the transaction once again automatically. Just now I use the AsyncTask of Android to communicate with the server:
new AsyncTask<String, Void, List<String>>() {
#Override
protected void onPreExecute(
showWaitDialog();
}
#Override
protected void onPostExecute(List<String> msgList) {
//here I put the handling after the POST ie. error and success handling
hideWaitDialog();
if (msgList.isEmpty() {
//success handling --> starting an new Activity
} else {
errorView.setText (...);
errorLayout.setVisibility (View.VISIBLE);
}
}
#Override
protected List<String> doInBackground(String... params) {
List<String> msgs = new ArrayList<String>();
try{
//for example submitting an JSONObject
JSONObject result = HttpUtils.sendHttpPost(
AppConstants.WEB_URL, jsonObject);
//error handling on the result
boolean hasErrors = JsonResult.isOk(result);
if (hasErrors) {
// adding errors to msgs list
String[] errorMessages = JsonResult.getErrorMessages (result,...);
fillList (msgs, errorMessages);
return msgs;
}
} catch (CommunicationError er) {
msgs.add (er...);
}
return msgs;
}
}
The problem with this approach is, that if I don't have a successful transmission of the data I must stay in the same Activity. Until now I show an error message to the user and he is in charge to submit by a button again the results to the server.
What I'm looking for is some Activity that remains persistent in the memory which runs later in the case that the transmission wasn't made.
As an application case I use this to dynamically upload pictures for a Waypoint in a map if I pressed that waypoint. In some case it can happens that the connection to the mobile service provider isn't available (mountains, forest, far apart from antenna). Then I want to leave the map Activity and switch to the detail view of this waypoint. In the success case I put the picture into my model classes and make an serialization. If the user clicks again on the same waypoint the picture is not loaded again. In the non success case I don't want to wait that the user clicks against on the waypoint to retrieve the image. In fact I need a background task, some sort of a queue that pictures of waypoints that are already visited on couldn't be retrieved are loaded until the communication part gives back a positive result and the image can be written into the model. The next time the user is pressing the Waypoint the picture will be then present.
Are there any best practices for making such a code implementation?
Is there any example around?
Is there a better way of doing this?
Yes, you need to Implement Intent Service for this requirement
According to the developers website
The IntentService class provides a straightforward structure for running an operation on a single background thread.
For complete details and working source code, Go through the Android Docs
Thanks to the answer of David.
I just read after the suggestion the tutorial at
[1] http://code.tutsplus.com/tutorials/android-fundamentals-intentservice-basics--mobile-6183
After my tests I prefered a Service (not an IntentService)
and created a service: SubmissionService
public class SubmissionIntentService extends Service {
private List<PendingMessage> pMsgList = new CopyOnWriteArrayList<PendingMessage>();
private Handler handler = new Handler();
private boolean hasAppStopped = false;
private Runnable runner;
public SubmissionIntentService() {
super();
Log.d (TAG, "Service created...");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
PendingMessage pMessage = (PendingMessage) intent.getParcelableExtra(AppConstants.MESSAGE_OBJECT);
synchronized (pMsgList) {
pMsgList.add(pMessage);
}
if (runner == null) {
handler.postDelayed(runner = initializeRunnable(), 500);
}
return Service.START_NOT_STICKY;
}
private void runAsLongAppIsActive (Runnable runner) {
if (!hasAppStopped) {
handler.postDelayed (runner, SOME_INTERVAL_CONSTANT);
}
}
private Runnable initializeRunnable() {
Runnable result;
result = new Runnable() {
#Override
public void run() {
if (pMsgList.isEmpty()) {
runAsLongAppIsActive (this);
return;
}
PendingMessage[] pMArray = null;
synchronized(pMsgList) {
pMArray = pMsgList.toArray (new PendingMessage[pMsgList.size()]);
}
if (pMArray==null || pMArray.length==0) {
runAsLongAppIsActive (this);
return;
}
Log.d (TAG, "Message List size is actually :"+pMArray.length);
for (PendingMessage pM: pMArray) {
try {
JSONObject jsonMess = JSONSendMessage.buildOutput (pM);
JSONObject result = HttupUtils.sendHttpPost (WEB_URL, jsonMess);
boolean hasErrors = JSONResult.isOk (result);
if (hasErrors) {
//TODO: error handling in case of transmission
//don't remove the message from the queue
runAsLongAppIsActive(this);
return;
}
//remove pending transmission of the queue if success
synchronized (pMsgList) {
pMsgList.remove (pM);
}
//inform over receiver if activity is shown
Intent broadcastIntent = new Intent();
//put data in intent
sendBroadcast (intent);
//more important
WayPointModel model = ModelInstance.getWayPointModel();
model.addToModel (pM, result);
model.store();
} catch (Exception e) {
continue; //try to send other messages
}
}
runAsLongAppIsActive (this);
}
};
return result;
}
}
#Override
public void onDestroy() {
hasAppStopped = true;
handler.removeCallbacks (runner);
super.onDestroy();
}
}
Further I added a ResponseReceiver:
public class ResponseReceiver extends BroadcastReceiver {
public static final String ACTION_RESP = "MESSAGE_PROCESSED";
#Override
public void onReceive(Context context, Intent intent) {
//work in progress...
}
}
and in the Activity where I want to be informed about events:
public class SomeActivity extends Activity {
private ResponseReceiver receiver;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IntentFilter filter = new IntentFilter(ResponseReceiver.ACTION_RESP);
filter.addCategory(Intent.CATEGORY_DEFAULT);
receiver = new ResponseReceiver();
registerReceiver(receiver, filter);
...
}
}
and finally to send messages over Http:
Intent msgIntent = new Intent(this, SubmissionIntentService.class);
msgIntent.putExtra(...);
startService(msgIntent);
don't forget to declare the service in your manifest:
<service android:name="ch.xxx.app.service.SubmissionIntentService" />
Observations:
- I called the method startService(...) from different Activities. The constructor is only called once.
==> I have just on instance of the service for all Activities (exactly what I need).
What I don't get until now:
- Putting back data to the Activity. What is if the Activity is at the moment no shown?
My problem is this;
I have a AsyncTask that works fine, and on doInBackground() it calls a new class that sync my data to a web service using REST service, i don't have everything on a unique class because i need the same content sync for different activitys and it's easier this way.
What i need is, on the sync procedure, i can get the number of "contacts" and everytime it downloads a contact, removes 1 from the "contacts" lenght, so, i nedd to show on the progress dialog the length of contact and refresh everytime it downloads a new "contact"
hre's my code for the AsyncTask:
public class syncContentTask extends AsyncTask<String, String, Boolean> {
private ProgressDialog mprogress;
private Context context;
//token for JSON header to authenticate
String authToken;
public syncContentTask(Context cxt, String token) {
this.context = cxt;
mprogress = new ProgressDialog(context);
authToken = token;
}
protected void onPreExecute() {
mprogress = ProgressDialog.show(context, "Sync", "Sync in progress...");
}
#Override
protected Boolean doInBackground(String... params) {
syncData syncData = new syncData();
syncData.syncData(context, authToken);
publishProgress(progress);
return true;
}
protected void onProgressUpdate(String... progress) {
//mprogress.setProgress(Integer.parseInt(progress[0]));
}
protected void onPostExecute(Boolean result) {
if (result) {
mprogress.dismiss();
}
}
}
In the Sync Data class i have functions that handles the HttpRequest and database stuff...
can anyone help??
You need to create a listener for your data progress and have it update the progress bar. Right now it looks like this line:
syncData.syncData(context, authToken);
blocks and no updates are provided to your progress indicator until it is done. So, you need something like:
MyListener listener = new MyListener(context);
SyncData syncData = new syncData(listener);
And in your listener have callback methods like myListener.downloadStarted() , myListener.updateProgressBar(int progress) and myListener.downloadCompleted() that your syncData class calls as the download progresses.
For example:
public abstract class SDScanAdapter implements SDScanListener {
public void startScan() {
}
public void updateScanProgress(int scanItemsTotal, int scanItemsCompleted) {
}
public void scanComplete() {
}
}
Then create a listener class:
public class ScanListener extends SDScanAdapter {
#Override
public void scanComplete(String contactName, String action) {
runOnUiThread(scanComplete);
}
#Override
public void startScan() {
runOnUiThread(startScan);
}
#Override
public void updateScanProgress(int scanItemsTotal,
int scanItemsCompleted) {
if (scanCountTotal != scanItemsTotal) {
scanCountTotal = scanItemsTotal;
progressBar.setMax(scanCountTotal);
}
if (scanCountUpdate != scanItemsCompleted) {
scanCountUpdate = scanItemsCompleted;
runOnUiThread(updateScanProgress);
}
}
}
And then for this example you need Runnables (startScan, scanComplete and updateScanProgress) that perform UI tasks, like updating the progress bar. In your case, you may also want to load some of the results, etc.
Then in your AsyncTask you do:
ScanListener listener = new ScanListener();
SyncData syncData = new syncData(listener);
Assuming the SDScanListener class and AsyncTask are all in your Activity. Also, your SyncData calss will need to have a SDScanListener variable that is set when it instantiates. Then, while it does its job, calls are made to the listener methods like:
scanListener.startScan();
And while it progresses, it calls the other methods (and corresponding parameters are passed in).