Android: DO WHILE AsyncTasks with OkHttpClient - java

I have an AsyncTask that sends data to a server. There is 1 call for each record in an SQL database. Once the HTTP call is competed, that record is marked as "uploaded". And I need to do this for all records that are not marked as "uploaded".
public void sync (final Context context, final boolean manualSync, final boolean rosterOnly, final String type) {
if ( unsentScansObjects == null ) {
Log.d(TAG, "sync: Loading Unsent Scans from Database...");
unsentScansObjects = unsentScans();
}
if ( unsentScansObjects.size() != 0 ) {
isUploading = true;
final ScanModel model = (ScanModel) unsentScansObjects.get(0);
UploadTask upload = (UploadTask) new UploadTask(mContext);
upload.completionBlock = new PPCompletionBlock() {
#Override
public void onCompletion(Boolean success, JSONObject object, String error) {
// Scan was marked as "uploaded", remove it from the array
unsentScansObjects.remove(model);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Keep calling sync until unsentScansObjects.size() == 0
sync(context, manualSync, rosterOnly, type);
}
},500);
}
};
upload.execute(model);
}
else {
isUploading = false;
unsentScansObjects = null;
}
}
Is there a more efficient way to upload all scans until there are none left?
I have tried a do { upload(); } while (unsentScansObjects.size() != 0);, but I need to wait until each http call and scan marked as "uploaded" is completed before moving on to the next.
I will note that this method works very well, but it was requested by client to have this as an "EOF", 1 call (not looping) type of function.

Related

Android, how to initiate realm in AsyncTask?

I have an asyncTask in my application as below. I have to fetch data from realm(which is successfully stored in another activity) into this AsyncTask. Below is my AsyncTask code:
public class MakeAsyncRequest extends AsyncTask<Object, String, MakeAsyncRequest.ResponseDataType>{
public Context asyncContext;
private MakeAsyncRequest(Context context){
asyncContext = context;
}
private static final OkHttpClient client = new OkHttpClient();
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private MakeRequest.ResponseHandler handler;
private String method;
private User user;
private Realm realm;
class ResponseDataType
{
InputStream inputStream;
String string;
String cookie;
}
MakeAsyncRequest(MakeRequest.ResponseHandler responseHandler, String type)
{
method = type;
handler = responseHandler;
}
#Override
protected ResponseDataType doInBackground(Object... params) {
try {
Requests requests = new Requests((Context) params[0]);
String url = params[1].toString();
String bodyJson = null;
if(method.equals("PUT") || method.equals("POST")) {
bodyJson = params[2].toString();
}
final Request.Builder builder;
Response response;
RequestBody body;
switch (method) {
case "GET": builder = new Request.Builder().url(url);
break;
case "DOWNLOAD": builder = new Request.Builder().url(url);
break;
case "POST": body = RequestBody.create(JSON, bodyJson);
builder = new Request.Builder()
.url(url)
.post(body)
.addHeader("Cookie", "key=value");
break;
case "PUT": body = RequestBody.create(JSON, bodyJson);
builder = new Request.Builder()
.url(url)
.put(body);
break;
default: builder = new Request.Builder().url(url);
}
builder.addHeader("Accept", "application/json");
realm = RealmController.initialize(this).getRealm();
final RealmResults<User> users = realm.where(User.class).findAllAsync();
user = users.first();
if(user.getCookie() !== null && !user.getCookie().isEmpty()){
builder.addHeader("cookie", "user.getCookie()");
}
response = client.newCall(builder.build()).execute();
ResponseDataType responseDataType = new ResponseDataType();
if(method.equals("DOWNLOAD")) { responseDataType.inputStream = response.body().byteStream(); }
else {
responseDataType.string = response.body().string();
responseDataType.cookie = response.headers().get("set-cookie");
CookieManager cManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL);
cManager.getCookieStore().getCookies();
}
return responseDataType;
} catch (IOException e) {
return null;
}
}
#Override
protected void onPostExecute(ResponseDataType response) {
try {
if (method.equals("DOWNLOAD")) {
handler.onFinishCallback(response.inputStream);
}else {
handler.onFinishCallback(response.string, response.cookie);
CookieManager cManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL);
cManager.getCookieStore().getCookies();
}
}catch (Exception e){
Log.d("hExceptionError", e.toString());
handler.onFinishCallback("{\n" +
" \"error\": \"error\"\n" +
"}","");
}
}
I am received the access error - "Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created." whenever the control tries to execute the Realm results or to get the first object from Realm.
Below is my RealmController which i created to control the realm instance:
public class RealmController {`public static RealmController initialize(Activity activity) {
if (instance == null) {
instance = new RealmController(activity.getApplication());
}
return instance;
}
public static RealmController initialize(MakeAsyncRequest activity) {
if (instance == null) {
instance = new RealmController(activity.);
}
return instance;
}
}`
I have my user model(Realm object) with setters and getters.
The point is - you cannot create and access Realm on different threads, i.e. create Realm instance in Activity and use it in .doInBackground() method. Create and release Realm immediately before and after transaction.
There may be another issue - don't register quer observer on background thread in AsyncTask - it doesn't have Looper initialized - use main thread or HandlerThread.
Release realm after it is no longer needed (you didn't in your code), because Realm has limited number of instances.
You can initiate a Realm instance in the doInBackground method of your AsyncTask like this:
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... voids) {
Realm realm = Realm.getDefaultInstance();
// Do your Realm work here
realm.close();
return null;
}
}
It's important to note that you should always open and close a Realm instance in the same thread. In this case, since the AsyncTask is running in a background thread, you can open and close the Realm instance within the doInBackground method.

Multiple Nested Callbacks from Async Class

Fairly new to Android/Java development and using the Open Source Parseplatform as my backend server. I've created a class to manage a parse object and this object update's its data from an async call to the parse server as per this code.
public class DeviceObject {
private String objectID, deviceName, status;
private ParseGeoPoint location;
int batLevel;
public DeviceObject(){
objectID = null;
deviceName = null;
location = null;
batLevel = 0;
status = null;
}
public void getDeviceLatestData() {
if (objectID != null) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("DeviceData");
query.whereEqualTo("DeviceObjectID", objectID);
query.orderByDescending("createdAt");
query.setLimit(1);
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> ParseDeviceList, ParseException e) {
if (e == null) {
if (ParseDeviceList.size() == 0) {
Log.d("debg", "Device not found");
} else {
for (ParseObject ParseDevice : ParseDeviceList) {
status = ParseDevice.getString("Status");
batLevel = ParseDevice.getInt("BatteryLevel");
location = ParseDevice.getParseGeoPoint("Location");
Log.d("debg", "Retrieving: " + deviceName);
Log.d("debg", "Status: " + status + " Battery: " + Integer.toString(batLevel));
}
//callback listener to add marker to map
}
} else {
Log.d("debg", "Error: " + e.getMessage());
}
}
});
}
}
So I create my class object in my Main Activity with the following:
DeviceObject userDevice = new DeviceObject();
userDevice.getDeviceLatestData();
What I can't get my head around is how in my MainActivity I can get notified/callback to continue displaying the information which the userDevice class just got off the parse Server.
I've tried creating an interface and adding a listener as what i've seen suggested however I could not add the listener inside the parse's done function.
The definition of my main activity is, note I need the OnMapReadyCallback as i'm using Google Maps
public class MapMainActivity extends AppCompatActivity implements OnMapReadyCallback {
So in summary i'd like to add something to the main activity so that I can process the data when it has been added to the class from the async call.
For something like this, I recommend using an event bus. Here is a link to a popular one I've had success with in the past.
Basically, you will have another class involved, which will be your bus. Your activity will register for a specific event (which you will create, subclassing as appropriate). Your async call will tell the event bus to fire off that event, and the bus will then tell all subscribers, including your main activity, that the event fired off. That is when you'd call getDeviceLatestData. Below are simple code snippets you may use, but read the documentation on that bus to fully understand it.
Your event:
public static class DataReady Event { /* optional properties */ }
Your DeviceObject:
public class DeviceObject {
private String objectID, deviceName, status;
private ParseGeoPoint location;
int batLevel;
public DeviceObject(){
objectID = null;
deviceName = null;
location = null;
batLevel = 0;
status = null;
}
public void getDeviceLatestData() {
if (objectID != null) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("DeviceData");
query.whereEqualTo("DeviceObjectID", objectID);
query.orderByDescending("createdAt");
query.setLimit(1);
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> ParseDeviceList, ParseException e) {
if (e == null) {
if (ParseDeviceList.size() == 0) {
Log.d("debg", "Device not found");
} else {
for (ParseObject ParseDevice : ParseDeviceList) {
status = ParseDevice.getString("Status");
batLevel = ParseDevice.getInt("BatteryLevel");
location = ParseDevice.getParseGeoPoint("Location");
Log.d("debg", "Retrieving: " + deviceName);
Log.d("debg", "Status: " + status + " Battery: " + Integer.toString(batLevel));
}
//callback listener to add marker to map
EventBus.getDefault().post(new DataReadyEvent());
}
} else {
Log.d("debg", "Error: " + e.getMessage());
}
}
});
}
}
Your MainActivity:
public class MainActivity {
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
#Subscribe(threadMode = ThreadMode.MAIN) // Seems like you're updating UI, so use the main thread
public void onDataReady(DataReadyEvent event) {
/* Do whatever it is you need to do - remember you can add properties to your event and pull them off here if you need to*/
};
}

Managing multiple Async tasks in android

Hey everyone so I am just starting a part two for online training app and trying to adapt my async task to get movie reviews from the movie db. Having a totally different async task just for that seems like there should be a better way. Here is the current async task implementation that only gets the movie data.
The question is how do I add another async task to this in order to retrive the movie reviews as well from this url /movie/{id}/videos.
public FetchMovieData(Context context, GridView grid, boolean sortType, ITaskCompleteListener listener) {
mContext = context;
this.mMoviesGrid = grid;
this.mSortByMostPopular = sortType;
this.mTaskCompleteListener = listener;
}
#Override
protected Void doInBackground(String... params) {
// These two need to be declared outside the try/catch
// so that they can be closed in the finally block.
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
try {
URL url;
if(mSortByMostPopular)
url = new URL(mContext.getString(R.string.picasso_url_popular_movies));
else
url = new URL(mContext.getString(R.string.picasso_url_highest_rated));
// Create the request to OpenWeatherMap, and open the connection
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
// Read the input stream into a String
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
if (inputStream == null) {
// Nothing to do.
mMovieJsonStr = null;
}
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
// Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
// But it does make debugging a *lot* easier if you print out the completed
// buffer for debugging.
buffer.append(line + "\n");
}
if (buffer.length() == 0) {
// Stream was empty. No point in parsing.
mMovieJsonStr = null;
}
mMovieJsonStr = buffer.toString();
} catch (IOException e) {
Log.e("PlaceholderFragment", "Error ", e);
// If the code didn't successfully get the weather data, there's no point in attempting
// to parse it.
mMovieJsonStr = null;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (final IOException e) {
Log.e("PlaceholderFragment", "Error closing stream", e);
}
}
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if(mMovieJsonStr != null)
Constants.mMovies = MovieDataParser.getMovieData(mMovieJsonStr);
mTaskCompleteListener.onTaskCompleted(); //Task completed alert UI that we have our data
}
So some one had suggested using Retrofit instead of having multiple async tasks. This seems like a good idea but I am having a lot of trouble understanding how it is supposed to work. Currently I have a WebService class an interface and am trying to use it to retrieve both movies and am going to add reviews then trailers. The issue is if I set the base url as "http://api.themoviedb.org" I get url must start with "/" in logcat.
Current code:
public class WebService {
public List<Movie> getMovies() {
RestAdapter retrofit = new RestAdapter.Builder()
.setEndpoint("http://api.themoviedb.org")
.build();
MovieDBService service = retrofit.create(MovieDBService.class);
return service.listMovies("movies");
}
}
public interface MovieDBService {
#GET("/3/discover/{switchterm}sort_by=popularity.desc&api_key=d273a1a1fb9390dab9 7ac0032b12366a")
List listMovies(#Path("switchterm") String switchterm);
}
//In code getting movies
WebService service = new WebService();
List movies = service.getMovies();
I think you have a lots of possibilities for doing this.You can follow this approach: add a second call to another AsyncTask when the first is finish, and pass to it a list of strings with the video ids:
public class FetchMovieData extends AsyncTask<Void, Void, Void> {
protected Boolean doInBackground() {
try {
String movieJSONString = getJSONMovies();
String[] ids = parseIdsFromJSON(movieJSONString);
if(ids.lenth != 0) {
FetchMovieReviews moviesReviewsAsyncTask = new FetchMovieReviews();
moviesReviewsAsyncTask.execute(ids);
} else {
return false;
}
return true;
} catch (Exception e) {
return false;
}
}
protected String getJSONMovies() {
//with the code you post, return the json string
}
protected String[] parseIdsFromJSON(String JSON) {
//parse the json and get the ids and return
//return {"1","2","3"}
}
}
public class FetchMovieReviews extends AsyncTask<String[], Void, Void> {
protected Void doInBackground(String[]... params) {
for(String id : params[0]) {
//call the web service and pass the id
}
return null;
}
}
You can put all the functionality for manage the calls to the web services in a MoviesRESTCalls class, and for manage the json in a MoviesJSONParser class or something like that, and the code is going to be much more clear.
So what I ended up with was this using the the Retrofit library for the web service. Thanks for the help everyone and let me know your thoughts.
public Context mContext;
private MovieJSON mMovieData;
private ReviewJSON mMovieReviews;
private VideoJSON mMovieVideos;
public boolean mSortByMostPopular;
ITaskCompleteListener mTaskCompleteListener;
public FetchMovieData(Context context, boolean sortType, ITaskCompleteListener listener) {
mContext = context;
this.mSortByMostPopular = sortType;
this.mTaskCompleteListener = listener;
}
public void getMovies() {
new FetchMovies().execute();
}
public void getReviews() {
new FetchReviews().execute();
}
public void getVideos() {
new FetchTrailers().execute();
}
private class FetchMovies extends AsyncTask<String, Void, Void > {
#Override
protected Void doInBackground(String... params) {
WebService service = new WebService();
//TODO Re-Implement sorting
mMovieData = service.getMovies();
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if(mMovieData != null)
Constants.mMovies = MovieDataParser.getMovieData(mMovieData);
mTaskCompleteListener.onTaskCompleted(); //Task completed alert UI that we have our data
}
}
private class FetchReviews extends AsyncTask<String, Void, Void > {
#Override
protected Void doInBackground(String... params) {
WebService service = new WebService();
mMovieReviews = service.getReviews();
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if(mMovieReviews != null)
Constants.mReviews = MovieDataParser.getReviewData(mMovieReviews);
mTaskCompleteListener.onTaskCompleted(); //Task completed alert UI that we have our data
}
}
private class FetchTrailers extends AsyncTask<String, Void, Void > {
#Override
protected Void doInBackground(String... params) {
WebService service = new WebService();
mMovieVideos = service.getVideos();
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if(mMovieVideos != null)
Constants.mTrailers = MovieDataParser.getVideoData(mMovieVideos);
mTaskCompleteListener.onTaskCompleted(); //Task completed alert UI that we have our data
}
}
//web service
public class WebService {
RestAdapter mRetrofit;
MovieDBService mService;
public WebService() {
mRetrofit = new RestAdapter.Builder()
.setEndpoint("http://api.themoviedb.org")
.build();
mService = mRetrofit.create(MovieDBService.class);
}
public MovieJSON getMovies() {
return mService.listMovies("");
}
public ReviewJSON getReviews() {
return mService.listReviews("76341");
}
public VideoJSON getVideos() {
return mService.listVideos("76341");
}
}

Multiple upload photo using LoopJ AndroidAsyncHttp

I'm going to upload multiple photo/video using LoopJ AndroidAsyncHttp to server. My problem is i need to add cancel button for each of the photo and allow the user to cancel the uploading. May i know anyone got the solution for this? or any others better example for me to refer?
My Code as below :-
public static void putMultipleUploadPhoto(String server,
final ProgressBar progressbarb, final String FileType, final TextView textviewb, final String FolderPath, final int itemcount, final int position)
{
final String url = "http://" + server + ":" + server.Photo_Upload;
File myFile = new File(data);
final RequestParams params = new RequestParams();
try {
params.put("data", myFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
final AsyncHttpClient client = new AsyncHttpClient();
final int totalprogress1 = 0;
try {
client.post(url,params,new AsyncHttpResponseHandler() {
public void onStart() {
// Initiated the request
progressbarb.setProgress(0);
}
#Override
public void onProgress(int position, int length) {
// TODO Auto-generated method stub
int totalprogress;
totalprogress = (position*100)/length;
progressbarb.setProgress(totalprogress);
super.onProgress(position, length);
}
#Override
public void onSuccess(String response) {
String regex = "\n"; // Only this line is changed.
String split[] = response.split(regex, 2);
if (split[0] != null)
{
String status[]=split[0].split("\\t");
if (status[0].equals("true"))
{
textviewb.setVisibility(View.VISIBLE);
textviewb.setText("Success");
if (status[0].equals("false"))
{
textviewb.setText("Fail";
textviewb.setVisibility(View.VISIBLE);
}
}
}
#Override
public void onFailure(Throwable e, String response) {
textviewb.setVisibility(View.VISIBLE);
textviewb.setText("Fail");
}
});
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
Very simple dear-
1)just send one by one image on server and then create a popup window for send next image or cancel.
2)In your database or where you have images just set flag 0 and 1. So you can easily make query
for send image on server which one is pending.
3)And when you got successes response from server change your flag value in database.

How to send a broadcast from within running thread

I've written a filecache that allows to load files from the web. Activities send requests to this filecache and give the name and action of a BroadcastReceiver that get's notified when the requested file has been downloaded.
This cache is working but it has a small drawback.
If there are lots of files in the download queue the activities are notified at once after processing all downloads. I would like to send the broadcast for each downloaded file.
Here's the stripped down code. Currently a Handler gets fired after processing all the files in the Thread.run(). I would like to send the Broadcast from within the run. What would be the prefered way to do something (send a Broadcast) during Thread.run()?
Many thanks in advance.
public abstract class MyFileCache {
private static class CacheElement {
private File file;
}
private static class QueueElement {
private long action;
private String filename = "";
private long id;
private String receiver = "";
}
private static class ProcessedElement {
private long action;
private File file;
private long id;
private String receiver = "";
}
private Map<String, CacheElement> cache = new ConcurrentHashMap<String, CacheElement>();
private Context context;
private Map<String, ProcessedElement> processed = new ConcurrentHashMap<String, ProcessedElement>();
private Map<String, QueueElement> queue = new ConcurrentHashMap<String, QueueElement>();
public MyFileCache(Context context) {
this.context = context;
}
private void doThread() {
final Handler handler = new Handler() {
#Override
public void handleMessage(Message message) {
try {
for (Map.Entry<String, ProcessedElement> entry : processed.entrySet()) {
// Currently: Processing all fetched files at once
// Send for all entries a broadcast to the requesting activities
ProcessedElement processedElement = entry.getValue();
if (processedElement != null && processedElement.receiver != null) {
processSendBroadcast(processedElement.receiver,
processedElement.action, processedElement.id);
}
deleteFromProcessed(entry.getKey());
}
} catch (NullPointerException exception) {
}
}
};
new Thread() {
#Override
public void run() {
for (Map.Entry<String, QueueElement> entry : queue.entrySet()) {
QueueElement queueElement = entry.getValue();
if (queueElement != null) {
File file = fetch(entry.getKey(), queueElement.id, queueElement.filename,
queueElement.receiver, queueElement.action);
if (file != null) {
// Wish: Sending a broadcast to the requesting activity for each fetched file
}
}
}
handler.sendEmptyMessage(0);
}
}.start();
}
private void deleteFromProcessed(String url) {
if (processed.containsKey(url)) {
ProcessedElement processedElement = processed.get(url);
if (processedElement != null) {
processed.remove(url);
}
}
}
// Send broadcast
private void processSendBroadcast(String receiver, long action, long id) {
Intent intent = new Intent();
intent.putExtra("ACTION", action);
intent.putExtra("ID", id);
intent.setAction(receiver);
context.sendBroadcast(intent);
}
}
I would say sending an Intent with a "FILE_COMPLETED" action, and adding a String extra with the file's name and have all your activities register a BroadcastReceiverwith that same action but compare whether or not the String extra within the Intentmatches the file that activity wanted.

Categories

Resources