I have a fragment activity and want to get data from my php script. I need this data to draw my ui effectively. My problem is my UI/fragment draws before i get data back, im not sure why as i fire it as early as i can in onCreate'. I put a dialog into pre and post to effectively freeze UI while data is retreived in background but....I dont see this happening, i think im too late in calling itas when the dialog appears during debug it shows ontop of a drawn screen which is baffling to me.
I have an alternative solution which is to fire the asyncTask in calling activity (previous activity) and pass result in bundle but i don't like this solution as its rigid and may cause issues with screen rotation.
I have been stuck on this for ages, can anybody tell me specifically where to put my async execute - the dialog should effectively make it a sync process. I have placed my asynctask everywhere i think possible/sensible and no luck.
In below i have the execute in the oncreate(). Note the execute doesnt d anything but update a test string which is "no change" beforehand, and "changed" in the postexecute so i can see what state its in at various points in code. It doesnt change before i draw my screen.
public class StaggeredGridActivityFragment extends FragmentActivity {
String test ="not changed";
private TilesAdapter mAdapter;
private ArrayList<String> mData;
private StaggeredGridView mGridView;
private static final String TAG = "StaggeredGridActivityFragment";
#Override
protected void onCreate(Bundle savedInstanceState) {
try
{
// Loading tile data in Background Thread
new GetLoginTiles().execute();
}
catch (Exception e)
{
e.printStackTrace();
}
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE); //remove title bar
final FragmentManager fm = getSupportFragmentManager();
// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
final StaggeredGridFragment fragment = new StaggeredGridFragment();
fm.beginTransaction().add(android.R.id.content, fragment).commit();
}
Intent i=getIntent();
Bundle extras = i.getExtras();
String tmp = extras.getString("myKey");
}
private class StaggeredGridFragment extends Fragment implements AbsListView.OnScrollListener, AbsListView.OnItemClickListener {
//private StaggeredGridView mGridView;
private boolean mHasRequestedMore;
// private TilesAdapter mAdapter;
//private ArrayList<String> mData;
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(R.layout.activity_sgv, container, false);
}
#Override
public void onActivityCreated(final Bundle savedInstanceState) {
List<NameValuePair> params = new ArrayList<NameValuePair>();
//Encapsulate all within a post cereate from a async task or call a blocking http call
super.onActivityCreated(savedInstanceState);
mGridView = (StaggeredGridView) getView().findViewById(R.id.grid_view);
if (savedInstanceState == null) {
final LayoutInflater layoutInflater = getActivity().getLayoutInflater();
View header = layoutInflater.inflate(R.layout.list_item_header_footer, null);
mGridView.addHeaderView(header);
}
if (mAdapter == null) {
mAdapter = new TilesAdapter(getActivity(), R.id.summary1_value);
}
if (mData == null) {
mData = ActivityTileData.getLoginTileDataArray(getActivity());
}
for (String data : mData) {
mAdapter.add(data); //Add each mData TileAdapter element to an mAdapter where it will be further broken down and used by the TileAdapter
}
mGridView.setAdapter(mAdapter);
mGridView.setOnScrollListener(this);
mGridView.setOnItemClickListener(this);
}
#SuppressLint("LongLogTag")
#Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
Log.d(TAG, "onScrollStateChanged:" + scrollState);
}
#SuppressLint("LongLogTag")
#Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {
Log.d(TAG, "onScroll firstVisibleItem:" + firstVisibleItem +
" visibleItemCount:" + visibleItemCount +
" totalItemCount:" + totalItemCount);
// our handling
if (!mHasRequestedMore) {
int lastInScreen = firstVisibleItem + visibleItemCount;
if (lastInScreen >= totalItemCount) {
Log.d(TAG, "onScroll lastInScreen - so load more");
mHasRequestedMore = true;
onLoadMoreItems();
}
}
}
//Loads all of the objects from the getLoginTileData() if called
private void onLoadMoreItems() {
while(mAdapter.getCount()<mData.size()) {
//final ArrayList<String> sampleData = SampleData.generateSampleData();
final ArrayList<String> loginTileData = ActivityTileData.getLoginTileDataArray(getActivity());
for (String data : loginTileData) {
mAdapter.add(data.toString());
}
// stash all the data in our backing store
mData.addAll(loginTileData);
// notify the adapter that we can update now
mAdapter.notifyDataSetChanged();
mHasRequestedMore = false;
}
}
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Toast.makeText(getActivity(), "Item Clicked: " + position, Toast.LENGTH_SHORT).show();
}
}
// Progress Dialog
private ProgressDialog qDialog;
// JSON parser class
JSONParser jParser = new JSONParser();
String url_login ="http://xxx/xxx.php";
/**
* Background Async Task to Load all images by making HTTP Request
* */
class GetLoginTiles extends AsyncTask<String, String, String> {
/**
* Before starting background thread Show Progress Dialog
* */
#Override
protected void onPreExecute() {
super.onPreExecute();
qDialog = new ProgressDialog(StaggeredGridActivityFragment.this);
qDialog.setMessage("Please wait...");
qDialog.setIndeterminate(false);
qDialog.setCancelable(false);
qDialog.show();
}
#Override
protected String doInBackground(String... args) {
// Building Parameters
List<NameValuePair> params = new ArrayList<NameValuePair>();
// getting JSON string from URL
JSONObject jsonLogin = jParser.makeHttpRequest(url_login, "GET", params);
test=jsonLogin.toString();
return jsonLogin.toString();
}
/**
* After completing background task Dismiss the progress dialog
* **/
protected void onPostExecute(String jsonString) {
// dismiss the dialog after getting all questions
qDialog.dismiss();
// updating UI from Background Thread
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into imagebuttons
* */
test="local test has changed";
}
});
}
}
}
I have an alternative solution which is to fire the asyncTask in calling activity (previous activity) and pass result in bundle but i don't like this solution as its rigid and may cause issues with screen rotation.
this is a good way to do it
override onSaveInstanceState to save the extras between rotations
also see here for more details
edit: it seems you are trying to change the text using
test="local test has changed";
whaat you need to do is pass the activity to the asynctask then
VIEWTYPEHERE button= ( VIEWTYPEHERE) activity.findViewById(R.id.YOUR_VIEW"S_ID_HERE);
button.setText("");
a couple of notes
on post execute runs on ui thread you don't need a new runnable
also you forgot to call .run() on it
Related
I'm trying to initialize my views with some details grabbed from my JSON Api.
I have a tablayout with multiple tabs(fragments) and in each fragment, I have placed an AsyncTask at the end of OnViewCreated ...
This works usually, but sometimes it errors out and says it cannot find elements of the view (NullPointerException)
I also notice that sometimes my AsyncTasks lag behind, swiping through the tabs sometimes gets ahead of the AsyncTasks and the tab content doesn't load for a couple of seconds after I've swiped over to them.
Is there a more correct way to do this?
Here's the code I'm working with right now (Same across each fragment of the tabview):
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mSwipeRefreshLayout = (SwipeRefreshLayout) getView().findViewById(R.id.swipe_refresh);
mSwipeRefreshLayout.setOnRefreshListener(this);
// Construct the data source
mSwipeRefreshLayout.setRefreshing(true);
mLoadTask = new LoadTask(this);
mLoadTask.execute((Void) null);
}
And here is an example of a load task:
public class LoadTask extends AsyncTask<Void, Void, Boolean> {
private String mResponse;
private Fragment mFrag;
public LoadTask(Fragment frag){
mFrag = frag;
}
#Override
protected Boolean doInBackground(Void... params) {
// Calls to functions for making an API request here
// Uses HttpUrlConnection
// Response is stored in mResponse
int responseCode = fullResponse.getStatus();
mResponse = fullResponse.getMessage();
Log.w("Response", mResponse);
if(responseCode == 200)
return true;
else
return false;
}
#Override
protected void onPostExecute(final Boolean success) {
mLoadTask = null;
if (success) {
// Initialize adapter and set it to the recyclerview
// Or call initializeView function which sets the view
// elements according the details of the response
} else {
// Error handling code
}
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
protected void onCancelled() {
mLoadTask = null;
}
}
I made simple Client server to android.
I have problem when I send an object from server to the client.
The object is received ok and when I check the log, it shows me the the object was sent successfully.
The problem occurs when I'm trying to get this object and put it in my ListView adapter.
The adapter works, I checked it with a random ArrayList I created.
My issue is when I'm trying to to put the values of AsyncTask in my adapter.
public class RestaurantListFragment extends Fragment {
private ArrayList<Resturant> res = new ArrayList<>();
private ListAdapter adapter;
private Firebase restRef = new Firebase("https://restmeup.firebaseio.com/restaurants");
private Client mClient;
// private connectTask t = (connectTask)new connectTask().execute();
public RestaurantListFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new connectTask().execute();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// new connectTask(getView()).execute();
final View rootView = inflater.inflate(R.layout.fragment_two, container, false);
ListView restaurantList = (ListView) rootView.findViewById(R.id.list);
adapter = new ListAdapter(getContext(), res, getActivity());
restaurantList.setAdapter(adapter);
// connectTask t = (connectTask)new connectTask().execute();
if (mClient != null) {
mClient.sendMessage("bar");
}
SqlQueriesConverter sql = new SqlQueriesConverter();
sql.getResurantsListQuery("bar");
// sql.getUserFavoritesResturants(accessToken.getUserId());
mClient.sendMessage(sql.getQuery());
// t.setArray(res);
mClient.sendMessage("/quit");
mClient.stopClient();
final EditText searchText = (EditText)rootView.findViewById(R.id.searchListView);
searchText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
System.out.println("Before---------");
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String text = searchText.getText().toString().toLowerCase(Locale.getDefault());
adapter.filter(text);
adapter.notifyDataSetChanged();
System.out.println("array: " + res.toString());
}
#Override
public void afterTextChanged(Editable s) {
System.out.println("After---------");
}
});
// Inflate the layout for this fragment
return rootView;
}
public class connectTask extends AsyncTask<ArrayList<?>,ArrayList<?>,Client> {
// private Client mClient;
private ArrayList<?> arrayList = new ArrayList<>();
#Override
protected Client doInBackground(ArrayList<?>... message) {
//we create a Client object and
mClient = new Client(new Client.OnMessageReceived() {
#Override
//here the messageReceived method is implemented
public void messageReceived(ArrayList<?> message) {
//this method calls the onProgressUpdate
// publishProgress(message);
onProgressUpdate(message);
}
});
mClient.run();
return null;
}
// #Override
protected void onProgressUpdate(ArrayList<?>... values) {
super.onProgressUpdate(values);
ArrayList<?> arr2;
if (values[0].get(0) instanceof Resturant){
Log.d("step 1", "1");
if (((ArrayList<?>)values[0]).get(0)instanceof Resturant) {
// arr2 = (ArrayList<Resturant>) values[0];
res = (ArrayList<Resturant>) values[0];
adapter.notifyDataSetChanged();
Log.d("array",res.toString());
}
}
if (values[0].get(0)instanceof Review){
arr2 = (ArrayList<Review>) values[0];
}
if (values[0].get(0)instanceof UserFavorites){
arr2 = (ArrayList<Review>) values[0];
Log.d("step 2", "2");
}
}
}
}
There are two things you need to change to use the AsyncTask as you intend. The first change you need is to return the ArrayList you get from your mClient in the doInBackground method. This is a bit troublesome because it looks like the Client is already running asynchronously since you pass a callback to get the result (this is the Client.OnMessageReceived anonymous class you have there). The second change would be to implement onPostExecute on your AsyncTask since that is where the results from doInBackground are sent. You'd add the result sent from doInBackground to your adapter in there.
In any case, since it looks like Client is already performing the work asynchronously, you shouldn't need to use an AsyncTask at all. Just implement the logic to add the results to your adapter in the Client.OnMessageReceived callback.
Just get the code you already have in onProgressUpdate and throw it inside messageReceived. Something like this:
mClient = new Client(new Client.OnMessageReceived() {
#Override
//here the messageReceived method is implemented
public void messageReceived(ArrayList<?> values) {
if (values[0].get(0) instanceof Resturant){
Log.d("step 1", "1");
if (((ArrayList<?>)values[0]).get(0)instanceof Resturant) {
res = (ArrayList<Resturant>) values[0];
adapter.notifyDataSetChanged();
Log.d("array",res.toString());
}
}
}
});
I have a fragment activity. When i click the fragment it fires a listener which fires an AsyncTask. I need the Async tasks result before moving to the next line of code in the listener i.e i need the asyncTask to be synchronous.
To do this i usually use a dialog to effectively make user wait for asyncTask onPostExecute(). But my dialog isnt appearing and my code is moving on past the asyncTask and into the bundle code which then adds null variables, sad face.
Here is the bones of my fragment class, let me know if you need anything else, i'm conscious of posting too much but i am sure its connected to the structure of my class and fact i'm using fragments.
public class Login_StaggeredGrid_Fragment_Activity extends FragmentActivity
{
private ArrayList<String[]> gameSummaryTilesData;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
final FragmentManager fm = getSupportFragmentManager();
// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
final StaggeredGridFragment fragment = new StaggeredGridFragment();
fm.beginTransaction().add(android.R.id.content, fragment).commit();
}
}
private class StaggeredGridFragment extends Fragment implements AbsListView.OnScrollListener, AbsListView.OnItemClickListener
{
private StaggeredGridView mGridView;
private boolean mHasRequestedMore;
private TilesAdapter mAdapter;
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(R.layout.activity_sgv, container, false);
}
#Override
public void onActivityCreated(final Bundle savedInstanceState) {
List<NameValuePair> params = new ArrayList<NameValuePair>();
//Encapsulate all within a post cereate from a async task or call a blocking http call
super.onActivityCreated(savedInstanceState);
mGridView = (StaggeredGridView) getView().findViewById(R.id.grid_view);
if (savedInstanceState == null) {
final LayoutInflater layoutInflater = getActivity().getLayoutInflater();
View header = layoutInflater.inflate(R.layout.list_item_header_footer, null);
mGridView.addHeaderView(header);
}
if (mAdapter == null) {
mAdapter = new TilesAdapter(getActivity(), R.id.summary1_value);
}
for (String[] data : loginTilesData) {
mAdapter.add(data); //Add each loginTilesData TileAdapter element to an mAdapter where it will be further broken down and used by the TileAdapter
}
mGridView.setAdapter(mAdapter);
mGridView.setOnScrollListener(this);
mGridView.setOnItemClickListener(this);
}
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id)
{
try
{
// Loading Games in Background Thread
new GetGamesSummaryTiles().execute();
}
catch (Exception e)
{
e.printStackTrace();
}
Intent i = new Intent(Login_StaggeredGrid_Fragment_Activity.this, GamesSummary_Fragment_Activity.class);
i.putExtra("gamesSummaryTilesData", gameSummaryTilesData);
startActivity(i);
}
}
/**
* Background Async Task to get data for next activity by making HTTP Request
* */
// Progress Dialog
private ProgressDialog qDialog;
// JSON parser class
JSONParser jParser = new JSONParser();
String url_login ="http://XX.XX.XXX.XX/XXXX.php";
class GetGamesSummaryTiles extends AsyncTask<String, String, String>
{
/**
* Before starting background thread Show Progress Dialog
* */
#Override
protected void onPreExecute()
{
super.onPreExecute();
qDialog = new ProgressDialog(getBaseContext());
qDialog.setMessage("Please wait...");
qDialog.setIndeterminate(false);
qDialog.setCancelable(false);
qDialog.show();
}
#Override
protected String doInBackground(String... args)
{
// Building Parameters
List<NameValuePair> params = new ArrayList<NameValuePair>();
// getting JSON string from URL
JSONObject jsonLogin = jParser.makeHttpRequest(url_login, "GET", params);
pk_http pk_dbComms = new pk_http();
try {
gameSummaryTilesData = pk_dbComms.formatHttpResponse_SummaryTile(jsonLogin);
} catch (JSONException e) {
String test = e.getStackTrace().toString();
e.printStackTrace();
}
return jsonLogin.toString();
}
/**
* After completing background task Dismiss the progress dialog
* **/
protected void onPostExecute(String jsonString)
{
// dismiss the dialog after getting all questions
qDialog.dismiss();
// updating UI from Background Thread
/*runOnUiThread(new Runnable() {
public void run() {
}
});*/
}
}
}
Put the your intent calling code in your onPostExecute method and your problem will be solved
move the below code from onItemClick() to onPostExceute
Intent i = new Intent(Login_StaggeredGrid_Fragment_Activity.this, GamesSummary_Fragment_Activity.class);
i.putExtra("gamesSummaryTilesData", gameSummaryTilesData);
startActivity(i);
put your below code in postExecute() methode of asyncTask...
Intent i = new Intent(Login_StaggeredGrid_Fragment_Activity.this, GamesSummary_Fragment_Activity.class);
i.putExtra("gamesSummaryTilesData", gameSummaryTilesData);
startActivity(i);
I have view pager. My viewpager contains 3 fragments. In the first fragment I have an AsyncTask class. I parsed JSON with AsyncTask and I can show it in listview. (everything is ok)
I have one problem AsyncTask which I have in the first fragment does not finish when I go to the next fragments. When I am in the second fragment my AsyncTask is also running. How can I write code to cancel my AsyncTask when viewpager's page changed?
This is my source (this is the first fragment source; another fragment source is the same but the only difference is the Server Url):
public class StradaChefs1 extends Fragment {
public static CustomerStatistic stat;
private ConnectionDetector con;
private AlertDialogManager alert = new AlertDialogManager();
#SuppressLint("ClickableViewAccessibility")
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.strada_chefs_1, container,
false);
stat = new CustomerStatistic();
con = new ConnectionDetector(getActivity());
if (!con.isConnectingToInternet()) {
alert.showAlertDialog(getActivity(),
"You have not internet connection");
} else {
stat.execute("my urlllllllll"); // geo
}
return rootView;
}
public class CustomerStatistic extends AsyncTask<String, Void, String> {
ProgressDialog pDialog;
#Override
protected void onPreExecute() {
super.onPreExecute();
pDialog = ProgressDialog.show(getActivity(), "Please Wait... ",
"Loading... ");
pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
pDialog.setCancelable(false);
pDialog.show();
}
#Override
protected String doInBackground(String... params) {
return Utils.getJSONString(params[0]);
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
try {
JSONArray mainJson = new JSONArray(result);
String first = mainJson.getString(0);
JSONObject jobject = new JSONObject(first);
String image = jobject.getString("image");
String String_title = jobject.getString("title");
String String_name = jobject.getString("name");
String String_desc = jobject.getString("description");
String second = mainJson.getString(1);
} catch (JSONException e) {
e.printStackTrace();
}
if (pDialog != null) {
pDialog.dismiss();
pDialog = null;
}
}
}
#Override
public void onResume() {
Log.e("DEBUG", "onResume of HomeFragment");
super.onResume();
}
#Override
public void onStop() {
super.onStop();
if (stat != null && stat.equals(AsyncTask.Status.RUNNING)) {
stat.cancel(true);
Toast.makeText(getActivity(), "finished", Toast.LENGTH_SHORT)
.show();
}
}
}
This is a viewpager java code
public class TabbedActivity1 extends Fragment {
private StradaChefs1 mfragment1;
private StradaChefs2 mfragment2;
private StradaChefs3 mfragment3;
private StradaChefs4 mfragment4;
SectionsPagerAdapter mSe;
public static final String TAG = TabbedActivity1.class.getSimpleName();
ViewPager mViewPager;
private ArrayList<Fragment> fragmentList;
public static TabbedActivity1 newInstance() {
return new TabbedActivity1();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.activity_item_one_1, container, false);
mSe=new SectionsPagerAdapter(getChildFragmentManager());
mViewPager = (ViewPager) v.findViewById(R.id.pager1);
CirclePageIndicator circle=(CirclePageIndicator)v.findViewById(R.id.circleindicator1);
mViewPager.setAdapter(mSe);
circle.setViewPager(mViewPager);
mfragment1 = new StradaChefs1();
mfragment2 = new StradaChefs2();
mfragment3 = new StradaChefs3();
mfragment4 = new StradaChefs4();
fragmentList = new ArrayList<Fragment>();
fragmentList.add(mfragment1);
fragmentList.add(mfragment2);
fragmentList.add(mfragment3);
fragmentList.add(mfragment4);
mViewPager.setPageTransformer(false, new PageTransformer() {
#Override
public void transformPage(View page, float position) {
page.setRotationY(position * -40);
}
});
return v;
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public int getCount() {
return 4;
}
#Override
public CharSequence getPageTitle(int position) {
return null;
}
}
}
How can I solve this problem?
The FragmentPagerAdapter keeps additional fragments, besides the one shown, in resumed state, so you can't use onPause and onResume for starting/stopping the AsyncTask. The solution is to implement a custom OnPageChangeListener and create a new method for when the fragment is shown.
1) Create LifecycleManager Interface The interface will have two methods and each ViewPager’s Fragment will implement it. These methods Are as follows:
public interface FragmentLifecycle {
public void onPauseFragment();
public void onResumeFragment();
}
2) Let each Fragment implement the interface
3) Implement interface methods in each fragment - in onPauseFragment stop the AsyncTask, in onResumeFragment start it
4) Call interface methods on ViewPager page change You can set OnPageChangeListener on ViewPager and get callback each time when ViewPager shows another page
5) Implement OnPageChangeListener to call your custom Lifecycle methods
Listener knows the new position and can call the interface method on new Fragment with the help of PagerAdapter. I can here call onResumeFragment() for new fragment and onPauseFragment() on the current one.
I need to store also the current fragment’s position (initially the current position is equal to 0), since I don’t know whether the user scrolled from left to right or from right to left. See what I mean in code:
private OnPageChangeListener pageChangeListener = new OnPageChangeListener() {
int currentPosition = 0;
#Override
public void onPageSelected(int newPosition) {
FragmentLifecycle fragmentToShow = (FragmentLifecycle)pageAdapter.getItem(newPosition);
fragmentToShow.onResumeFragment();
FragmentLifecycle fragmentToHide = (FragmentLifecycle)pageAdapter.getItem(currentPosition);
fragmentToHide.onPauseFragment();
currentPosition = newPosition;
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) { }
public void onPageScrollStateChanged(int arg0) { }
};
I didn't write the code. Full tutorial here
When you start the asynctask set the flag isRunning=true
when you are trying to jump from one fragment to other it mean
as per fragment lifecycle your are pausing and stoping your current fragment
so in onStop method of fragment you can check isRunning flag of asynctask is true if yes the
cancel the asyntask
its my logic hope it will help you to achieve your requirement
I'm trying to use an AsyncTaskLoader to load data in the background to populate a detail view in response to a list item being chosen. I've gotten it mostly working but I'm still having one issue. If I choose a second item in the list and then rotate the device before the load for the first selected item has completed, then the onLoadFinished() call is reporting to the activity being stopped rather than the new activity. This works fine when choosing just a single item and then rotating.
Here is the code I'm using. Activity:
public final class DemoActivity extends Activity
implements NumberListFragment.RowTappedListener,
LoaderManager.LoaderCallbacks<String> {
private static final AtomicInteger activityCounter = new AtomicInteger(0);
private int myActivityId;
private ResultFragment resultFragment;
private Integer selectedNumber;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myActivityId = activityCounter.incrementAndGet();
Log.d("DemoActivity", "onCreate for " + myActivityId);
setContentView(R.layout.demo);
resultFragment = (ResultFragment) getFragmentManager().findFragmentById(R.id.result_fragment);
getLoaderManager().initLoader(0, null, this);
}
#Override
protected void onDestroy() {
super.onDestroy();
Log.d("DemoActivity", "onDestroy for " + myActivityId);
}
#Override
public void onRowTapped(Integer number) {
selectedNumber = number;
resultFragment.setResultText("Fetching details for item " + number + "...");
getLoaderManager().restartLoader(0, null, this);
}
#Override
public Loader<String> onCreateLoader(int id, Bundle args) {
return new ResultLoader(this, selectedNumber);
}
#Override
public void onLoadFinished(Loader<String> loader, String data) {
Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId);
resultFragment.setResultText(data);
}
#Override
public void onLoaderReset(Loader<String> loader) {
}
static final class ResultLoader extends AsyncTaskLoader<String> {
private static final Random random = new Random();
private final Integer number;
private String result;
ResultLoader(Context context, Integer number) {
super(context);
this.number = number;
}
#Override
public String loadInBackground() {
// Simulate expensive Web call
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Item " + number + " - Price: $" + random.nextInt(500) + ".00, Number in stock: " + random.nextInt(10000);
}
#Override
public void deliverResult(String data) {
if (isReset()) {
// An async query came in while the loader is stopped
return;
}
result = data;
if (isStarted()) {
super.deliverResult(data);
}
}
#Override
protected void onStartLoading() {
if (result != null) {
deliverResult(result);
}
// Only do a load if we have a source to load from
if (number != null) {
forceLoad();
}
}
#Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
result = null;
}
}
}
List fragment:
public final class NumberListFragment extends ListFragment {
interface RowTappedListener {
void onRowTapped(Integer number);
}
private RowTappedListener rowTappedListener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
rowTappedListener = (RowTappedListener) activity;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(getActivity(),
R.layout.simple_list_item_1,
Arrays.asList(1, 2, 3, 4, 5, 6));
setListAdapter(adapter);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
ArrayAdapter<Integer> adapter = (ArrayAdapter<Integer>) getListAdapter();
rowTappedListener.onRowTapped(adapter.getItem(position));
}
}
Result fragment:
public final class ResultFragment extends Fragment {
private TextView resultLabel;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.result_fragment, container, false);
resultLabel = (TextView) root.findViewById(R.id.result_label);
if (savedInstanceState != null) {
resultLabel.setText(savedInstanceState.getString("labelText", ""));
}
return root;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("labelText", resultLabel.getText().toString());
}
void setResultText(String resultText) {
resultLabel.setText(resultText);
}
}
I've been able to get this working using plain AsyncTasks but I'm trying to learn more about Loaders since they handle the configuration changes automatically.
EDIT: I think I may have tracked down the issue by looking at the source for LoaderManager. When initLoader is called after the configuration change, the LoaderInfo object has its mCallbacks field updated with the new activity as the implementation of LoaderCallbacks, as I would expect.
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
LoaderInfo info = mLoaders.get(id);
if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
if (info == null) {
// Loader doesn't already exist; create.
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
if (DEBUG) Log.v(TAG, " Created new loader " + info);
} else {
if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
if (info.mHaveData && mStarted) {
// If the loader has already generated its data, report it now.
info.callOnLoadFinished(info.mLoader, info.mData);
}
return (Loader<D>)info.mLoader;
}
However, when there is a pending loader, the main LoaderInfo object also has an mPendingLoader field with a reference to a LoaderCallbacks as well, and this object is never updated with the new activity in the mCallbacks field. I would expect to see the code look like this instead:
// This line was already there
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
// This line is not currently there
info.mPendingLoader.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
It appears to be because of this that the pending loader calls onLoadFinished on the old activity instance. If I breakpoint in this method and make the call that I feel is missing using the debugger, everything works as I expect.
The new question is: Have I found a bug, or is this the expected behavior?
In most cases you should just ignore such reports if Activity is already destroyed.
public void onLoadFinished(Loader<String> loader, String data) {
Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId);
if (isDestroyed()) {
Log.i("DemoActivity", "Activity already destroyed, report ignored: " + data);
return;
}
resultFragment.setResultText(data);
}
Also you should insert checking isDestroyed() in any inner classes. Runnable - is the most used case.
For example:
// UI thread
final Handler handler = new Handler();
Executor someExecutorService = ... ;
someExecutorService.execute(new Runnable() {
public void run() {
// some heavy operations
...
// notification to UI thread
handler.post(new Runnable() {
// this runnable can link to 'dead' activity or any outer instance
if (isDestroyed()) {
return;
}
// we are alive
onSomeHeavyOperationFinished();
});
}
});
But in such cases the best way is to avoid passing strong reference on Activity to another thread (AsynkTask, Loader, Executor, etc).
The most reliable solution is here:
// BackgroundExecutor.java
public class BackgroundExecutor {
private static final Executor instance = Executors.newSingleThreadExecutor();
public static void execute(Runnable command) {
instance.execute(command);
}
}
// MyActivity.java
public class MyActivity extends Activity {
// Some callback method from any button you want
public void onSomeButtonClicked() {
// Show toast or progress bar if needed
// Start your heavy operation
BackgroundExecutor.execute(new SomeHeavyOperation(this));
}
public void onSomeHeavyOperationFinished() {
if (isDestroyed()) {
return;
}
// Hide progress bar, update UI
}
}
// SomeHeavyOperation.java
public class SomeHeavyOperation implements Runnable {
private final WeakReference<MyActivity> ref;
public SomeHeavyOperation(MyActivity owner) {
// Unlike inner class we do not store strong reference to Activity here
this.ref = new WeakReference<MyActivity>(owner);
}
public void run() {
// Perform your heavy operation
// ...
// Done!
// It's time to notify Activity
final MyActivity owner = ref.get();
// Already died reference
if (owner == null) return;
// Perform notification in UI thread
owner.runOnUiThread(new Runnable() {
public void run() {
owner.onSomeHeavyOperationFinished();
}
});
}
}
Maybe not best solution but ...
This code restart loader every time, which is bad but only work around that works - if you want to used loader.
Loader l = getLoaderManager().getLoader(MY_LOADER);
if (l != null) {
getLoaderManager().restartLoader(MY_LOADER, null, this);
} else {
getLoaderManager().initLoader(MY_LOADER, null, this);
}
BTW. I am using Cursorloader ...
A possible solution is to start the AsyncTask in a custom singleton object and access the onFinished() result from the singleton within your Activity. Every time you rotate your screen, go onPause() or onResume(), the latest result will be used/accessed. If you still don't have a result in your singleton object, you know it is still busy or that you can relaunch the task.
Another approach is to work with a service bus like Otto, or to work with a Service.
Ok I'm trying to understand this excuse me if I misunderstood anything, but you are losing references to something when the device rotates.
Taking a stab...
would adding
android:configChanges="orientation|keyboardHidden|screenSize"
in your manifest for that activity fix your error? or prevent onLoadFinished() from saying the activity stopped?