Android fragments: AsyncTask IO error handling - java

I have a main activity which use view pager to display 3 tabs. In each tab fragment, I may call two AsyncTask classes to fetch data from the my web service. When the background task failed to connect to the web service, I would like to handle the ConnectException by closing the main activity(that has the 3 tabs) and redirect to errorActivity. What is the best way to do this?
I have set launchMode="singleTop" in the manifest file.
Currently, what I do for each AsyncTask is:
private class FetchDataTask extends AsyncTask<Void, Void, ArrayList<Person>> {
#Override
protected ArrayList<Person> doInBackground(Void... params) {
try {
return new Fetcher().fetchPeople();
} catch (IOException e) {
Log.e(TAG, "Failed to fetchPeople");
cancel(true);
return null;
}
}
#Override
protected void onPostExecute(ArrayList<Person> people) {
mPeople = people;
….
}
#Override
protected void onCancelled() {
super.onCancelled();
Intent i = new Intent(getActivity(), ErrorActivity.class);
i.putExtra(ErrorFragment.EXTRA_ERROR_MSG, ErrorFragment.MSG_UNEXPECTED_ERROR);
startActivity(i);
getActivity().finish();
}
}
But the problem I am facing is having to open multiple ErrorActivity considering that the first two tabs will be called upon start up.

Create a function in your activity called onConnectionFailed() and in all fragments when service call fails call getActivity() typecaste it into your activity and call onConnectionFailed().
You can also create an interface with this function, make you activity implement that function and provide the body for it, and finally pass the activity as the object of that interface to the fragments.
inside onConnectionFailed() just call finish()
EDIT 1:
onWebServiceCallFailed(){
if(!error){
Intent i = new Intent(this,
i.putExtra(ErrorFragment.EXTRA_ERROR_MSG, ErrorFragment.MSG_UNEXPECTED_ERROR);
startActivity(i);
this.finish();
error = true;
}
}
And inside activity create a boolean error = false

Related

How to preserve the state of android activity

I have two activities my MainActivity and some other one called DetailActivity. When the app is first started it opens MainActivity and there it binds DataService and makes a call to fetch some data and populate a list view.
From that list view user has a button to open so called detail view for every item in the list. Opening that detail view means starting the second activity (DetailActivity).
Its done like this:
final Intent intent = new Intent(getContext(), DetailActivity.class);
intent.putExtra("data", dto);
getContext().startActivity(intent);
When second one is opened user is able to go back either by using back button (one left of home button on android) or by clicking back arrow in the header.
Everything works as expected except that when user comes back to MainActivity DataService is binded again and call to fetch the data is made and the list is updated. So if user is somewhere at item no. 205 he will be returned back to the start item.
Is there a way to hold the data or the state of MainActivity when user comes back to it that its not refreshed ?
Service is bonded like this
#Override
protected void onStart() {
super.onStart();
bind(DataService.class);
}
#Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
private void bind(final Class... toBind) {
for (final Class clazz : toBind) {
bindService(new Intent(this, clazz), connection, BIND_AUTO_CREATE);
}
}
private ServiceConnection connection = new ServiceConnection() {
#Override
public void onServiceConnected(final ComponentName name, final IBinder service) {
if (service instanceof DataService.LocalBinder) {
dataService = ((DataService.LocalBinder) service).getInstance();
dataService.readData();
}
}
#Override
public void onServiceDisconnected(final ComponentName name) {
// Empty By Default
}
};
You can achieve this by binding the service in onCreate() instead of onStart().
You should also have a look at the symmetry of your life cycle. Currently you are binding in start and unbinding in destroy. If you bind in onStart you should probably unbind in onStop. When you move the binding to onCreate you can keep the unbinding in onDestroy
Explanation: onCreate() is called when the activity is created first. onStart() is called every time your activity becomes visible.
Managed to solve it like this
#Override
public boolean onSupportNavigateUp() {
onBackPressed(); // one inherited from android.support.v4.app.FragmentActivity
return false;
}
Doing it this way seamed to do the trick. I would return to the place where I left from and there is no need to load any data since everything is already there

Send message to previous fragment

I have two fragmnent.Say fragment A and fragment B.Now after clicking on certain button in fragment A I am starting fragment B using below code
getFragmentManager()
.beginTransaction()
.replace(R.id.framelayout, companyDetailsFragment)
.addToBackStack(null)
.commit();
Now I have another back button in fragment B.After clicking that button
I am removing that particular fragment using below code
getFragmentManager().popBackStack()
Now what I want is when the user click on back button I want to pass some certain data to previous fragment A.And the problem is
onStart() method is not getting called,so I am not getting any values.
So how to get the data?Any help will be appreciated.
I am able to solve it.Here is my answer
1.Create an interface
public interface OnButtonPressListener {
public void onButtonPressed(String msg);
}
2.Implemented this in Fragment B
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
buttonListener = (OnButtonPressListener) getActivity();
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement onButtonPressed");
}
}
back.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getFragmentManager().popBackStack();
buttonListener.onButtonPressed("Message From First Fragment");
}
});
3.Used that listener in the Activity class
public class ParentActivity extends FragmentActivity implements OnButtonPressListener {
#Override
public void onButtonPressed(String msg) {
FragmentA Obj=(FragmentA) getSupportFragmentManager().findFragmentById(R.id.framelayout);
Obj.setMessage(msg);
}
}
4.Created the method in Fragment A class
public void setMessage(String msg){
System.out.print("got it");
}
Got the reference from this .Hope this will help others.If anyone has other good solution please answer this question.
You have a few options:
Store the value you want to pass in a globally scoped variable (SharedPrefs, using a singleton accessible from your Application class, etc).
Start a new instance of your Fragment, and include the variable as a Intent Extra.
AFAIK, there is not a way to add extras to a fragment when starting from the stack (you would access this in the onResume() if the variable could be passed).

Android: how do I tell my activity to start new activity from my service?

I have a service that is, among other things, downloading images from internet. When this is done I want to show this image in a custom Activity that has a dialog theme. But I only want to use this pop up if the app is running, otherwise I just want to create a notification.
But I get an exception when I try to start an activity from my service and i feel that maybe this isn't the right way to do it?
It says:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
So my question is if this is the right way to do this by setting that flag or how should I get my downloaded image from my service to an activity. Can I in some way tell an activity to start a new activity from my service class?
I think using Broadcast Receiver is better option for you.
Add Below Method in Service and call this method when image Download Complete.
private void updateMyActivity(Context context) {
if(MainActivity.activityStatusFlag){
//update the activity if activityStatusFlag=true;
Intent intent = new Intent("mUpdateActivity");
context.sendBroadcast(intent);
}else{
//display notification if activityStatusFlag=false;
}
}
In Activity Add Following Code.
public class MainActivity extends Activity{
public static boolean activityStatusFlag= false;
//define this variable to check if activity is running or not.
#Override
protected void onResume() {
super.onResume();
activityStatusFlag = true;
this.getApplicationContext().
registerReceiver(mMessageReceiver,new IntentFilter("mUpdateActivity"));
}
#Override
protected void onPause() {
super.onPause();
activityStatusFlag = false;
this.getApplicationContext().unregisterReceiver(mMessageReceiver);
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//Display Popup or update Activity
}
};
}

finish() method is not working inside of AsyncTask

I know finish() method is used to finish current activity while starting a new activity. But probably finish() method is not working in my AsyncTask override method.
Okay, My current activity is LoginActivity in which I implemented AsyncTask and in one of my override methods of AsyncTask I am starting LoggedInActivity. In LoggedInActivity there are many fragments. This doesn't cause any problem I think so. When I press BACK button I get LoginActivity. I don't want that. Please take a look at my code:
public class LoginActivity extends Activity implements OnClickListener {
private void startLoggedInActivity()
{
Intent i = new Intent(this, LoggedInActivity.class);
startActivity(i);
finish();
}
private class FetchProfileTask extends AsyncTask<String, Integer, JSONObject>
{
protected JSONObject doInBackground(String... strings) {
bla....bla....
}
protected void onPreExecute() {
bla....bla....
}
protected void onPostExecute(JSONObject jsonObject) {
try {
startLoggedInActivity();
} catch (Exception e) {
Log.w("Exception", "FetchProfileTask - onPostExecute()");
} finally {
}
}
}
}
I will be pleased if anyone helps me.....
Add android:noHistory="true" to LoginActivity in your manifest.
What this does is mark the activity to not be added to your Activity stack after going to another activity. So when you press the back button, it will close your app rather than going back to the LoginActivity. With this approach, you no longer need the finish() call.
<activity
...
android:noHistory="true">
The back button will change to the last activity. If the activity is finished, Android will create it again(I'm not very sure).So I think you can rewrite the back button.
Activity activity; // Instance Variable
Inside On Create :
activity = this;
private void startLoggedInActivity()
{
Intent i = new Intent(this, LoggedInActivity.class);
startActivity(i);
activity.finish();
}

OrientationChange doesn't destroy activity?

I have a rather commonly occurring situation in Android, which has to do with the previous asynctask updating the activity, whilst the activity has been lost because of a change in orientation.
I have an activity, Activity A.
Activity A implements OnDownloadCompleteListener {
public void sync()
{
new SyncAttestationInfoTask(this).execute();
}
#Override
public void onComplete()
{
loadAttestationInfo();
}
}
Here is my asynctask shortened:
package com.evento.mofa.backgroundtasks;
import java.util.ArrayList;
import java.util.List;
/**
* #author Ahmed
*
*/
public class SyncAttestationInfoTask extends AsyncTask<Void, Void,Void> {
/*TIP*/
//TO SPEED UP THIS OPERATION WE CAN USE THE EXECUTEONEXECUTOR .
private ProgressDialog pd;
private OnDownloadComplete parentActivityContext;
EntityConvert convert = new EntityConvert();
private AttestationDao aDao = new AttestationDao();
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
if (Locale.getDefault().getLanguage().equals("ar"))
{
/*EMPTY ALL THE TABLES THEN START PROCESSING*/
aDao.flushTables(Locale.getDefault().getLanguage());
syncAttestLocations(Webservices.ATTEST_LOCATION_AR,1);
syncDocumentTypes(Webservices.DOCUMENT_TYPES_AR,1);
syncAttestationInfo(Webservices.ATTESTATION_INFO_AR,1);
} else {
/*EMPTY ALL THE TABLES THEN START PROCESSING*/
aDao.flushTables(Locale.getDefault().getLanguage());
syncAttestLocations(Webservices.ATTEST_LOCATION,0);
syncDocumentTypes(Webservices.DOCUMENT_TYPES,0);
syncAttestationInfo(Webservices.ATTESTATION_INFO,0);
}
return null;
}
public SyncAttestationInfoTask(OnDownloadComplete context) {
parentActivityContext = context;
}
#Override
protected void onPreExecute() {
pd = new ProgressDialog((Context)parentActivityContext);
pd.setTitle("Loading...");
pd.setMessage("Updating Data.");
pd.setCancelable(false);
pd.setIndeterminate(true);
pd.show();
}
#Override
protected void onPostExecute(Void result) {
pd.dismiss();
parentActivityContext.onComplete();
// findViewById(R.id.the_button).setEnabled(true);
}
}
There is something strange with my Activity.
I put a breakpoint on the onComplete callback inside my activity
I start a progress dialog inside the sync async task.
As soon as the progress dialog displays on the screen I landscape my device.
The dialog box vanishes, and pd.dismiss() raises a "View not attached" error (I understand that the activity that it was attached to no longer exists).
The above means that parentActivityContext().oncomplete should also throw the same error, however it does not.
I commented the pd.Dismiss(), and found out that the breakpoint on onComplete() is invoked? Isn't this strange given the fact that the reference to the activity has been lost at this point?
Please give me insight into this.
I would do this
Add this line to your Manifest.xml file, this will prevent of calling onCreate() when screen rotates.
<activity android:name=".yourActivity" android:configChanges="keyboardHidden|orientation">
Version above Android 3.2, you also need to add "screenSize":
<activity android:name=".yourActivity" android:configChanges="keyboardHidden|orientation|screenSize">
This will prevent activity from restarting on orientation change, and you should not have any problems (except maybe some layout fixes)
The previous Activty is being referenced by the AsyncTask via a strong reference.
AsyncTask constructed with Activity 1 (parentActivityContex =
Activity 1)
Activity 1 "destroyed" and Activity 2 comes into foreground
AsyncTask completes, calls parentActivityContext (Activity 1) onComplete
Activity 2 just sits there doing nothing
Activity 1 no longer has active references pointing to it, is collected
You could try doing your task in a Service and having whatever Activity is in the foreground receive a broadcast, or you could try having your AsyncTask reference a Fragment with setRetainInstance(true). Below is an example of the second case. Note that you might want to handle the case where the AsyncTask completes while the Fragment is detached from one Activity and not yet attached to the next Activity
public class ExampleDownloadFragment extends Fragment {
#Override
public void onCreate(final Bundle savedInstanceState) {
setRetainInstance(true);
new SyncAttestationInfoTask(this).execute();
}
public void onComplete() {
final Activity activity = getActivity();
if (activity != null && activity instanceof A) {
((A) activity).onComplete();
}
}
}

Categories