Global Loader (LoaderManager) for reuse in multiple Activities / Fragments
V

4

16

What I would like to achieve:

I have two different fragments. I would like them both to show the same data in two forms (in a list and on a map). I would like them to share one Loader (AsyncTaskLoader in particular). Everything works fine, but the Loader isn't re-used. Another one is created and the data is loaded twice.

What I do:

In the Fragments I use LoaderManager lm = getActivity().getSupportLoaderManager(); In both of them I implement LoaderCallbacks<ArrayList<Item>> and the required methods. In both I use lm.initLoader(0, args, this);.

But when I output the lm.toString() it appears that these are two different Loaders. And the data is downloaded twice.

How to re-connect to the same Loader from a different Activity/Fragment than the one it was started in?

It should be possible since the context is attached to the Loader anyway on every onCreate(), e.g. on configuration change.

Veronicaveronika answered 6/4, 2012 at 15:23 Comment(5)
can you explain why you need to reference the Loader in both the Fragment and its parent Activity? keep in mind that it is encouraged to design your Fragments for the purpose of reuse... things can get messy pretty quickly if you begin to intertwine the explicit behavior of your Activitys and Fragments... try and do as much work as you can within each class separately, and then implement Activity callback methods when appropriate.Vidavidal
that being said, i'll give you a chance to explain your reasoning before i start sounding too pedantic :PVidavidal
Oh no, you misunderstood. It's not its parent Activity! It's a very different Activity. The sense of the question would remain the same if I asked about two fragments. How to re-use one Loader in two different fragments? It's just that I use a Fragment inside some Activity for the list and a MapActivity for the map, I thought that maybe it matters but I don't think so.Mayan
I figured I'd visit this quesiton once again, since it wasn't completely resolved. I've been in the process of studying the LoaderManager/Loader source code over the past few days just to fully grasp how it works, and it seems like something like this shouldn't be possible. Each activity/fragment gets its own LoaderManager (its not a global instance), and the LoaderManager starts/stops/destroys the Loaders as necessary in order to manage them across the activity/fragment lifecycle. However you may reuse Loaders that arent used with the LoaderManager. More on this later (new blog post soon)Vidavidal
Too late for this comment, but why not just load the data once and assign it to a global List variable? Or use interfaces?Joannejoannes
V
6

How to re-connect to the same Loader from a different Activity/Fragment than the one it was started in?

You should not reuse Loaders that are being managed by a LoaderManager instance across multiple Activitys and Fragments.

The LoaderManager will start/stop those Loaders with respect to the Activity/Fragment lifecycle, so there is no way of guaranteeing that those Loaders will exist once you are in another Activity.

From the documentation:

LoaderManager.LoaderCallbacks is a callback interface that lets a client interact with the LoaderManager.

Loaders, in particular CursorLoader, are expected to retain their data after being stopped. This allows applications to keep their data across the activity or fragment's onStop() and onStart() methods, so that when users return to an application, they don't have to wait for the data to reload. You use the LoaderManager.LoaderCallbacks methods when to know when to create a new loader, and to tell the application when it is time to stop using a loader's data.

In other words, it is often the case that your Loaders will be specific to some Activity (or Fragment). When you have your Activity implement the LoaderManager.LoaderCallbacks interface, your Activity is given type LoaderManager.LoaderCallbacks. Each time you call initLoader(int ID, Bundle args, LoaderCallbacks<D> callback), the LoaderManager either creates or reuses a Loader that is specific to some instance of the LoaderManager.LoaderCallbacks interface (which in this case is an instance of your Activity). This essentially binds your Activity with a Loader, and its callback methods will be called as the loader state changes.

That being said, unless you can find a way to have your two separate Activitys share the same callback methods, I doubt there is a clean way to do this (i.e. having an Activity and a Fragment share the same callbacks sounds like it would be tricky, if not impossible). I wouldn't worry about it too much though. In all of the sample code I have ever seen, I've never seen two Activitys and/or Fragments share the same callback methods. Further, given that Activitys and Fragments are both supposed to be designed for reuse, sharing Loaders in this way just doesn't seem like something that would be encouraged.

Vidavidal answered 7/4, 2012 at 18:24 Comment(11)
A-ha! So it's not bound to Activity, but to LoaderCallbacks. I don't think that it would be impossible to write an external class that extends LoaderCallbacks. Thanks for that! You say that it's not encouraged. But for me it seems a perfect way to use the same data to show it to the user in two different ways. On a list and on a map in my example. Don't you think? Now I use the Loader to download it and I write it to the application Context. It's not perfect, because if the loader in list didn't finish it's job and user switched to map, then the same loader is started again.Mayan
"...find a way to have your two separate Activitys share the same callback methods, I doubt there is a clean way to do this" - I would like someone to explain to us if it's possible. From what I think, it is, because it's the LoaderManager which is bound to Activities/Fragments lifecycle, not LoaderCallbacks. But maybe I'm wrongMayan
Hmm... yes, that makes sense. Perhaps you could try having your Activitys extend a class that implements LoaderManager.LoaderCallbacks. That way, they will both inherit the same callbacks and perhaps you can reuse the Loader that way. I've never actually tried to do this... let me know if you figure something out and I'll update my answer :).Vidavidal
Now I'm dealing with something else, but as soon as I get back to the matter, I'll let you know in the comments.Mayan
To be honest, I don't think that this is something you should really need to worry about... it might end up causing you more trouble than its worth.Vidavidal
Eventually I decided to leave as it is, because currently I can see no downsides of this approach. Maybe if I change it in the future or someone finds out how to do it, there'll be another answerMayan
Yeah, like I said... I've never seen anyone re-use a Loader across multiple Activity's before. Not even in the open-source apps made by Google itself. I'm also interested in knowing whether or not it is possible though :).Vidavidal
@MichałK I've been looking into this A LOT recently and I've come to the conclusion that you definitely cannot share Loaders that are managed by two separate LoaderManager instances (i.e. you can't share the same Loader across two Activitys if both Activitys use a LoaderManager to manage the Loaders). You can only share a Loaders that are not associated with a LoaderManager across multiple activities/fragments. I'm writing up a blog post on this right now :)Vidavidal
Looking forward to seeing the post then:) Btw, I dropped the idea and started using ContentProviders which doesn't answers the question but solved my problems.Mayan
I'm coming to this late, but I'm wondering if anyone has tried implementing the LoaderManager.LoaderCallbacks interface on a derivation of the Application object as a means to a "global" solution.Mogilev
Would it be reasonable to create a separate class to implement 'LoaderCallbacks'? The problem I see is it will need a 'Context' in order for the 'onCreateLoader' implementation to create a 'Loader'. I would prefer a 'has a' relationship for code reuse over the 'is a' one created by inheritance. Any thoughts on how to do this an not leak a Context object?Oddson
L
1

Yes. It worked for me. I have 3 different Fragments in a Navigation Drawer where the same data is populated in different ListViews. (All Fragments are a part of the SAME Activity).

My AsyncTaskLoader:

public class MyTaskLoader extends AsyncTaskLoader<HashMap<String, Integer>> {

public MyTaskLoader(Context context) {
    super(context);
}

@Override
public HashMap<String, Integer> loadInBackground() {
...
return hashMap;
}

...
}

Use the Same Loader Id in all Fragments.

Fragment1:

public class Fragment1 extends BaseFragment implements LoaderManager.LoaderCallbacks<HashMap<String, Integer>> {
@Override
public void onCreate(Bundle savedInstanceState) {

//initialize adapter

getActivity().getSupportLoaderManager().initLoader(0, null, this);

}

@Override
public Loader<HashMap<String, Integer>> onCreateLoader(int arg0, Bundle arg1) {
    // TODO Auto-generated method stub

    return new MyTaskLoader(getActivity());
}

@Override
public void onLoadFinished(Loader<HashMap<String, Integer>> arg0,
        HashMap<String, Integer> data) {
    // TODO Auto-generated method stub

    listAdapter.setData(data.keySet());

}

@Override
public void onLoaderReset(Loader<HashMap<String, Integer>> arg0) {
    // TODO Auto-generated method stub

    listAdapter.setData(null);
}
}

Use the same Id for Fragment2:

public class Fragment2 extends BaseFragment implements LoaderManager.LoaderCallbacks<HashMap<String, Integer>> {
@Override
public void onCreate(Bundle savedInstanceState) {

//initialize adapter

getActivity().getSupportLoaderManager().initLoader(0, null, this);

}

@Override
public Loader<HashMap<String, Integer>> onCreateLoader(int arg0, Bundle arg1) {
    // TODO Auto-generated method stub

    return new MyTaskLoader(getActivity());
}

@Override
public void onLoadFinished(Loader<HashMap<String, Integer>> arg0,
        HashMap<String, Integer> data) {
    // TODO Auto-generated method stub

    listAdapter.setData(data.keySet());

}

@Override
public void onLoaderReset(Loader<HashMap<String, Integer>> arg0) {
    // TODO Auto-generated method stub

    listAdapter.setData(null);
}
}

The adapter should be initialized before initializing the loader. Works so far. But, is this the right way? Is there a better method for using a common loader for multiple Fragments?

Lutherlutheran answered 6/1, 2015 at 5:42 Comment(2)
I think this is fine - it works because you're using the (common) Activity's supportLoaderManager plus the same loader ID, so the same loader just gets used multiple times. I can't see any reason why it wouldn't be OK? And it's nice and simple, no need to cache or create another listener-broadcaster set and manage them.Gman
Unfortunately, this didn't work. The loader was only being recreated in the onCreate() method of every Fragment. Data is loaded every time a Fragment is Created.Lutherlutheran
K
0

I'm not quite sure what you're trying to archieve after going through the discussions. But there is an application.registerActivityLifecycleCallbacks() method, which accept global activity lifecycle listeners (like onActivityCreated()).

Krystakrystal answered 14/4, 2013 at 8:13 Comment(1)
This is an old discussion. This "reused loader" appeared to be completely useless when I really understood the android pattern of downloading and caching data (using ContentProvider etc.).Mayan
G
0

I think you'll have the desired behavior if you use the same Loader ID across your different Fragments and Activities. Make sure the loader ID are unique to the data to load though. PhotosLoader and VideoLoader shouldn't have the same id for example.

Gourmand answered 31/8, 2016 at 19:56 Comment(2)
Because of Firebase? Or am I missing something? BTW, there was this article this year: medium.com/google-developers/…Gourmand
I meant Rxjava, but generally, Loaders were OK, but there are better alternatives.Mayan

© 2022 - 2024 — McMap. All rights reserved.