How can I tie an AsyncTaskLoader to a Fragment's lifecycle? - android

I am using the support library and am using an AsyncTaskLoader. Why doesn't the constructor of the AsycTaskLoader not accepting a Fragment as a parameter?
I only want the AsyncTaskLoader to start loading data when it is called or initialized inside my fragments. But as of now, at anytime the Activity goes to onResume it restarts all the loaders I initialized on different fragments. I believe this is mainly because I am passing fragment.getActivity() in the constructor of my AsyncTaskLoader instances.
Any way to do this?
So far, I am wrapping the initialization of the loaders in a fragment and each have an inner AsyncTaskLoader, which I customized as well. Then when the fragment is initialized, in the onCreateView method, I then call the method like so :
initLoader();
initLoader() method
public Loader<Object> initLoader() {
return getLoaderManager().initLoader(LOADER_ID.DUMMIES, null, new LoaderCallbacks<Object>() {
#Override
public Loader<Object> onCreateLoader(int id, Bundle args) {
Loader<Object> loader = new CustomLoader<Object>(getActivity()) {
#Override
public Object loadInBackground() {
return DummyGenerator.generateDummyEntriesToDb();;
}
};
return loader;
}
#Override
public void onLoadFinished(Loader<Object> loader, Object data) {
setToDb(data);
}
#Override
public void onLoaderReset(Loader<Object> loader) {
}
});
}
CustomLoader.java - generic implementation I suited to my needs. The releaseResources method is not filled in but I left it there for future usage.
public class CustomLoader<T> extends AsyncTaskLoader<T> {
T mData;
public CustomLoader(Context context) {
super(context);
}
#Override
public T loadInBackground() {
return null;
}
#Override
public void deliverResult(T data) {
if (isReset()) {
releaseResources(data);
return;
}
T oldData = mData;
mData = data;
if (isStarted()) {
super.deliverResult(data);
}
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
#Override
protected void onStartLoading() {
if (mData != null) {
deliverResult(mData);
}
if (takeContentChanged() || mData == null) {
forceLoad();
}
}
#Override
protected void onStopLoading() {
cancelLoad();
}
#Override
protected void onReset() {
onStopLoading();
if (mData != null) {
releaseResources(mData);
mData = null;
}
}
#Override
public void onCanceled(T data) {
super.onCanceled(data);
releaseResources(data);
}
public void releaseResources(T data) {
}
}
The generateDummyEntriesToDb method is working fine just creating list of the objects I am using, as well as the setToDb() method. The problem is when the activity goes to onResume the loadnBackground() method is called again thus I am compelled to think that all the other loaders behave the same way.

The Context provided should be the Activity attached to the Fragment. Be sure you are initializing the loader via the LoaderManager as it is tied to the appropriate object life cycle. So when initializing from within a Fragment, you should use Fragment.getLoaderManager(). Then call LoaderManager.initLoader() appropriately.

Related

Refreshing a Loader without discarding previous data

I'm writing an Android application that uses an AsyncTaskLoader handled by a LoaderManager to acquire some data. The data can be modified upstream when the app is open, but as loading the data is time-consuming I check if it has been modified first.
I cache the result and the last-modified field, and my loadInBackground() method first checks if the upstream data has been modified before loading the actual data. Checking the upstream last-modified field is also time-consuming, and therefore must be done inside the AsyncTaskLoader, not on the UI thread.
public class DataActivity extends Activity implements LoaderManager.LoaderCallbacks<LoadedData> {
// ...
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
getLoaderManager().initLoader(0, null, this);
}
private void reloadData() { // called from various locations
getLoaderManager().restartLoader(0, null, this);
}
#Override
public Loader<LoadedData> onCreateLoader(int i, Bundle bundle) {
return new DataLoader(this);
}
#Override
public void onLoadFinished(Loader<LoadedData> loader, LoadedData result) {
setActivityLoadingState(false);
updateShownData(result);
}
#Override
public void onLoaderReset(Loader<LoadedData> loader) {}
private static class DataLoader extends AsyncTaskLoader<LoadedData> {
private LoadedData lastData;
private int lastModified = -1;
GameListLoader(DataActivity activity) {
super(activity);
}
#Override
public LoadedData loadInBackground() {
int currentModified = getUpstreamLastModified();
if (currentModified == lastModified)
return lastData;
LoadedData currentData = getUpstreamData();
lastData = currentData;
lastModified = currentModified;
return currentData;
}
#Override
protected void onStartLoading() {
forceLoad();
setActivityLoadingState(true);
}
}
}
Now, I noticed that the LoaderManager.restartLoader method creates a new Loader every time, which discards my cache entirely, and loads the data every time.
Is there a way to ask the AsyncTaskLoader to refresh (i.e. call its startLoading, as I have onStartLoading calling forceLoad) from the LoaderManager? Or should I not be using LoaderManager or AsyncTaskLoader at all?

getLoaderManager().initLoader(); always returns a new loader when using support.v4.fragment

I'm using android.support.v4.content.AsyncTaskLoader to load data into a support.v4.fragmentbut when the configuration changes i.e : rotate the screen getLoaderManager().initLoader(); always returns a new loader thus loadInBackground() is called again . When I tried to use a normal fragment not the support.v4 version and changed to the normal AsyncTaskLoader every thing worked as expected ,so i'm not sure if this is a bug in the support library or what?
TestAsync.class:
import android.support.v4.content.AsyncTaskLoader;
public class TestAsync extends AsyncTaskLoader<List<Movie>> {
public TestAsync(Context context) {
super(context);
}
#Override
public void deliverResult(List<Movie> data) {
if (isReset()) {
// An async query came in while the loader is stopped. We
// don't need the result.
if (data != null) {
onReleaseResources(data);
}
}
List<Movie> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(data);
}
// At this point we can release the resources associated with
// 'oldApps' if needed; now that the new result is delivered we
// know that it is no longer in use.
if (oldData != null) {
onReleaseResources(oldData);
}
}
#Override
protected void onStartLoading() {
super.onStartLoading();
if (mData != null || oneShot){
deliverResult(mData);
}else {
forceLoad();
}
}
#Override
protected void onStopLoading() {
cancelLoad();
}
#Override public void onCanceled(List<Movie> data) {
super.onCanceled(data);
// At this point we can release the resources associated with 'apps'
// if needed.
onReleaseResources(data);
}
/**
* Handles a request to completely reset the Loader.
*/
#Override protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
// At this point we can release the resources associated with 'apps'
// if needed.
if (mData != null) {
onReleaseResources(mData);
mData = null;
}
}
protected void onReleaseResources(List<Movie> apps) {
// For a simple List<> there is nothing to do. For something
// like a Cursor, we would close it here.
}
}
BrowseMoviesActivityFragment:
public class BrowseMoviesActivityFragment extends Fragment implements LoaderManager.LoaderCallbacks<List<Movie>> {
#Override
public Loader<List<Movie>> onCreateLoader(int id, Bundle args) {
return new TestAsync(mContext);
}
#Override
public void onLoadFinished(Loader<List<Movie>> loader, List<Movie> data) {
if (data != null) {
if (adapter.isEmpty()){
adapter.add(data);
gridView.setAdapter(adapter);
}else {
adapter.add(data);
adapter.notifyDataSetChanged();
}
}
}
#Override
public void onLoaderReset(Loader<List<Movie>> loader) {
adapter = new BrowseMoviesAdapter(getActivity(),new ArrayList<Movie>());
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mContext = activity;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Bundle args = new Bundle();
getLoaderManager().initLoader(id, args, this);
}
}
BrowseMoviesActivity:
import android.support.v7.app.AppCompatActivity;
public class BrowseMoviesActivity extends AppCompatActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_browse);
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
BrowseMoviesActivityFragment browseFragment = (BrowseMoviesActivityFragment) fm.findFragmentByTag(TAG_BROWSE_FRAGMENT);
if (savedInstanceState == null){
if (browseFragment==null){
browseFragment = new BrowseMoviesActivityFragment();
fm.beginTransaction().replace(R.id.browse_container, browseFragment).commit();
}
}

Asynctaskloader restart loading when the activity restart

Hi I'm implementing a custom asynctaskloader to load data in the background. The problem is when the user goes back to the activity after he minimizes the app(or pick photo from gallary), all the asynctaskloaders associated with the activity and asynctaskloaders associated with the fragments in the activity start all over again.
How can I prevent the loader to restart the loading process when the activity restarts?
My baseAsynctaskLoader class:
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import android.util.Log;
public abstract class BaseAsyncTaskLoader extends AsyncTaskLoader{
private static final String TAG = "BaseAsyncTaskLoader";
protected Context context;
protected Object mData;
public BaseAsyncTaskLoader( Context context ) {
super(context);
Log.d(TAG, "BaseLoader");
this.context = context.getApplicationContext();
}
#Override
public abstract Object loadInBackground();
#Override
public void deliverResult(Object data){
Log.d( TAG, "deliverResult" );
if (isReset()) {
return;
}
mData = data;
if (isStarted()) {
super.deliverResult(data);
}
}
#Override
protected void onStartLoading(){
Log.d( TAG, "onStartLoading" );
if (mData != null) {
deliverResult(mData);
}
if (takeContentChanged() || mData == null) {
forceLoad();
}
}
#Override
protected void onStopLoading(){
Log.d( TAG, "onStopLoading" );
cancelLoad();
}
#Override
protected void onReset(){
Log.d( TAG, "onReset" );
super.onReset();
onStopLoading();
if(mData !=null){
mData=null;
}
}
#Override
public void onCanceled(Object data){
Log.d( TAG, "onCanceled" );
super.onCanceled(data);
}
}
Loader class sample:
public class AddDetailService extends BaseAsyncTaskLoader{
Activity activity;
Bundle bundle;
String outputstringrespone="";
public AddCarService(Activity activity, Bundle bundle){
super(activity);
this.activity = activity;
this.bundle = bundle;
}
#Override
public Object loadInBackground(){
try{
int userId = bundle.getInt(USER_ID);
int modelId = bundle.getInt(MODEL_ID);
outputstringrespone = addDetails(userId, modelId);
}catch(Exception e){
Log.d(TAG, e.toString());
}
return null;
}
#Override
public void deliverResult(Object data){
super.deliverResult(data);
Log.d(TAG,"output--"+outputstringrespone);
if(outputstringrespone.equalsIgnoreCase("Success")){
Toast.makeText(activity, "Details Added Successfully", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(activity, "Details not added",Toast.LENGTH_SHORT).show();
}
}
}
LoaderCallback in activity:
getLoaderManager().initLoader(LoaderId.ADD_DETAIL, bundle, addDetailsCallbacks);
LoaderManager.LoaderCallbacks addDetailsCallbacks = new LoaderManager.LoaderCallbacks(){
#Override
public Loader onCreateLoader(int id, Bundle args){
return new AddDetailService(getActivity(), args);
}
#Override
public void onLoadFinished(Loader loader, Object data){
getLoaderManager().destroyLoader(LoaderId.ADD_DETAIL);
}
#Override
public void onLoaderReset(Loader loader){
}
};
This is the desired behavior of LoaderManager framework - it takes care of reloading the data with the initiated Loaders in case the enclosing Activity or Fragment get re-created.
In fact, some implementations of Loaders do not reload the data, but simply provide access to the latest data that has been cached internally.
Bottom line: you observe correct behavior of LoaderManager framework.
It is not clear what it is you're trying to accomplish with your Loader, but it looks like you chose an incorrect tool. If your goal is to perform the action just once, then you should use AsyncTask instead of Loaders.

Custom AsyncTaskLoader, loadinBackground not called after 5 attempts

I have a SherlockListFragment that implements a custom AsyncTaskLoader.
In the overridden onStartLoading(), I have:
#Override
protected void onStartLoading() {
if (mData != null) {
deliverResult(mData);
}
else{
forceLoad();
}
}
The containing SherlockListFragment initiates the loader in onActivityCreated:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new MyListAdapter(getActivity());
setListAdapter(mAdapter);
getLoaderManager().initLoader(0, null, this);
}
and :
#Override
public Loader<List<MyData>> onCreateLoader(int id, Bundle args) {
return new MyListLoader(getActivity());
}
The problem is that after 5 activations/navigations to my FragmentActivity, loadinBackground() is not called. The onStartLoding is called, as well as the forceLoad, but that's it.
No Exception, nothing in the LogCat.
Any ideas?
It is Ok to call forceLoad().
See what documentation says:
You generally should only call this when the loader is started - that is, isStarted() returns true.
Full code:
#Override
protected void onStartLoading() {
try {
if (data != null) {
deliverResult(data);
}
if (takeContentChanged() || data == null) {
forceLoad();
}
Log.d(TAG, "onStartLoading() ");
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
}
Important:
documentation says: Subclasses of Loader<D> generally must implement at least onStartLoading(), onStopLoading(), onForceLoad(), and onReset().
AsyncTaskLoader extends Loader but not implements onStartLoading(), onStopLoading(), onReset(). You must implement it by yourself!
P.S. I was confused with it after experience of using simple CursorLoader, I also thought that using forceLoad() is bad practice. But it is not true.

Android Loader not triggering callbacks on screen rotate

I am using an AsyncTaskLoader. I have an activity which has implemented LoaderCallbacks (Support library).
I have breakpoint debugged and put in logs, the loader delivers the result, however the second time the callback onLoadFinished is not triggered. The odd thing - when I rotate back again it works, which ever orientation I start on gets callbacks when I return to it.
In my Activity onResume:
LoaderManager lm = getSupportLoaderManager();
Loader loader = lm.initLoader(0, null, new LoaderManager.LoaderCallbacks<String>() {
#Override
public Loader<String> onCreateLoader(int i, Bundle bundle) {
Loader<String> loader = new TestLoader(MainActivity.this);
return loader;
}
#Override
public void onLoadFinished(Loader<String> stringLoader, String s) {
Log.d(Application.TAG, "OnLoadFinished " + s);
doStuff(s);
}
#Override
public void onLoaderReset(Loader<String> stringLoader) {
// NOP
}
});
In my loader:
public class TestLoader extends AsyncTaskLoader<String> {
private String mData;
public TestLoader(Context context) {
super(context);
}
// This get's called after a loader is initialized or a loader
// that is alive still is reset
#Override
public void onStartLoading() {
if (mData != null) { // Have our data loaded, just deliver it!
deliverResult(mData);
}
if (takeContentChanged() || mData == null) {
forceLoad();
}
}
// This is called when an Activity or Fragment requests a loader
// to be reset because they want new data
#Override
public void onReset() {
mData = null;
// Ensure that the old task is cancelled if it was running
// We do NOT have to call forceLoad here because onStartLoading
// will get called after this
cancelLoad();
}
// Here we just want to store our own data we got and reset our boolean
#Override
public void deliverResult(String data) {
Log.d(Application.TAG, "deliverResult " + data);
mData = data;
super.deliverResult(mData);
}
#Override
public String loadInBackground() {
// returns content from a content provider ...
}
}
Really baffled by this one, I am new to Android so maybe this is obvious to someone else :)
You must at least simply call getSupportLoaderManager() / getLoaderManager() in onCreate() if it's an Activity or onActivityCreated() if it's a Fragment. The actual initLoader() can be elsewhere. Otherwise the loader will be in a stopped state and won't deliver the results even though it completes the load. I suspect it's because the loader manager doesn't reattach the old loaders to the new Activity unless the above call is made in the new Activity's onCreate().
You have
Loader loader = lm.initLoader(...)
You should have
Loader loader = new LoaderManager.LoaderCallbacks(...) {...}
and in your onResume()
this.getLoaderManager().restartLoader(0, null, this.loader);
See Loaders documentation.

Categories

Resources