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.
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.
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?
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()).
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.
Related
At school we're now learning on how to make fragments more universal by using interfaces.
This technique is still kinda abstract and I don't really know when/how to use it.
Can anybody point me to some resources on how to use that technique (Could it be called interface callbacks?)
All help is very appreciated!
The callback approach, as you would call it, is as simple as Listener interface found in many parts of Java or Android. You may check the Observer pattern if you want to learn about a very general description. But if you already understand how to work with Listener, you will easily get the point about callbacks.
NOTE: Do not mix it with Callback term - these are not the same.
Suppose we have Activity MyActivity and Fragment MyFragment. We want to post some data from Fragment to Activity. Then let us create an interface within MyFragment:
public class MyFragment extends Fragment{
private PostDataCallback mCallback;//our Activity will implement this
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (PostDataCallback) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
public interface PostDataCallback{
public void onPostData(Object data);
}
/*
we trigger this method when we calculated
data or something like that and want to post it*/
public void onSomeEvent(Object data){
mCallback.onPostData(data);
}
}
Our MyActivity will look like this:
public class MyActivity extends Activity implements MyFragment.PostDataCallback{
private Object data;
#Override
protected void onCreate(Bundle savedInstanceState){
getFragmentManager().beginTransaction().add(R.id.some_container_id, new MyFragment(), "my fragment");
}
#Override
public void onPostData(Object data){
this.data = data;
//some operations
}
}
So, MyFragment knows nothing about the implementation of it's callback. But it knows, that it can call the method onPostData(Object o) on the instance of PostDataCallback, which is held in the variable mCallback.
Thus, when MyFragment triggers it's mCallback.onPostData(data), MyActivity get's the result.
Exactly the same approach would work if we wanted to send message from MyActivity to MyFragment, but we would do it do it vice versa: the trigger method, callback interface definition and instance would reside in MyActivity, and MyFragment would implement the interface.
Here are steps:
Download sample data from http://developer.android.com/training/basics/fragments/index.html(given in right side) and also look at url to how to add fragments from xml or dynamically to performing fragment transaction operations..
Then would recommend you to go through with fragment guide..http://developer.android.com/guide/components/fragments.html
Once you understand complete life cycle and its fragment callback methods then would be easy to understand example given by Google as sample.
To defining interface in fragment to calling interface or passing callback to activity..
Let’s say you have two fragments which shows list as article titles and article details.
In your article list extends fragment list public class Fragment1 extends ListFragment
Set your list view using list adapter in oncreateview method.
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, Array);
setListAdapter(adapter);
Now we need to display article details when user click on article, so we need to pass position to activity to it can call back corresponding article details to show in fragment2.
So when user click on article, system call onListItemClick callback method.
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Call interface here and pass article position
Define interface and pass position in method which activity will override.
public interface OnArticleSelectedListener {
public void onArticleSelected(int position);
}
In on attach method instantiates an instance of interface by casting the Activity, If the activity has not implemented the interface, then the fragment throws a ClassCastException. On success.
Override interface method to display article details by passing position as bundle data to Fragment2.
Hope it will help you to understand sample code.
You can simple create new Android Application project in eclipse.
Then create Android Object (Fragment) with callback methods. This will give you an idea for interfaces.
And then the same you can apply for activity to fragment.
does anybody have any elegant solution for refreshing the Views in Fragments in a FragmentActivity's ViewPager after a sync Service from a SyncAdapter runs?
I've tried calling notifyDataSetChanged() and notifyDataSetInvalidated() on my adapter, as well as refreshDrawableState() on my views (GridViews), but to no avail. Perhaps I've been calling them from the wrong places -- I've tried doing it at setUserVisibleHint where isVisible=true, hoping to trigger it whenever the fragment comes into view, but it doesn't work.
I've also been using ASync calls to the SQLite database for my data needs, rather than a Content Provider, which I think would have made this a bit easier. I can think of a couple of ways to do it without a Content Provider, but neither are very nice.
Any ideas? I can provide code if wished. Thanks.
I'll assume that you're using an AsyncTask for loading the cursor just for the sake of the explanation, but it would work the same if you're using a Loader, an ThreadPool or whatever.
From the service, as soon as new data was changed I would send a LocalBroadcast. The activity might be there or not, so a broadcast is a good way to let it know there's new data. So from the service you would do:
// that's an example, let's say your SyncAdapter updated the album with this ID
// but you could create a simply "mybroadcast", up to you.
Intent i = new Intent("albumId_" + albumId);
LocalBroadcastManager.getInstance(this).sendBroadcast(i);
and then from the activity/fragment that have the Cursor, you'll be listening to this broadcast like this:
public void onResume(){
// the filter matches the broadcast
IntentFilter filter = new IntentFilter("albumId_" + albumId);
LocalBroadcastManager.getInstance(this).registerReceiver(myReceiver, filter);
}
public void onPause(){
LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver);
}
// and of course you have to create a BroadcastReceiver
private BroadcastReceiver myReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent){
// here you know that your data have changed, so it's time to reload it
reloadData = new ReloadData().execute(); // you should cancel this task onPause()
}
};
as I said, this next part varies depending on what threading method you're using to load the Cursor, for this example I'll show in a AsyncTask because it's very popular (but I really believe you and every developer in the world should use the Loaders pattern).
private class ReloadData extends AsyncTask<Void, Void, Cursor> {
protected Cursor doInBackground(Void... void) {
// here you query your data base and return the new cursor
... query ...
return cursor;
}
protected void onPostExecute(Cursor result) {
// you said you're using a subclass of CursorAdater
// so you have the method changeCursor, that changes the cursor and closes the old one
myAdapter.changeCursor(result);
}
}
The above approach I tested and used before and I know it works. There's a way of making it work with the flag FLAG_REGISTER_CONTENT_OBSERVER and override onContentChanged() to re-execute the query and swap the cursor, but I've never tested it. It will be something like that:
init your adapter with the constructor CursorAdapter(Context context, Cursor c, int flags) passing the flag FLAG_REGISTER_CONTENT_OBSERVER and override onContentChanged(). Inside onContentChanged you will execute the AsyncTask just like above. This way you don't have to use the LocalBroadcastManager as the database will alert. The reason that method is not my main answer, it's because I've never tested it.
Note that autoRequery have been deprecated and it's discouraged as it performs data loading in the UI thread.
edit:
I just noticed that the content observer is an API 11 thing. You have two options: 1 use the support library instead: https://developer.android.com/reference/android/support/v4/widget/CursorAdapter.html or the broadcast option.
Register a BroadcastReceiver in the fragments you have and in its onReceive call refresh - this method is supposed to update the UI depending what you have inside. For making your code easy to use, have a base Fragment class and do the registering/unregistering there along with an abstract refresh method that will be implemented by children fragments. Something like:
public abstract class BaseRefreshableFragment extends Fragment {
private BroadcastReceiver refreshReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if ("package_name.REFRESH_ACTION".equals(intent)) {
refresh();
}
}
};
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
IntentFilter filter = new IntentFilter("package_name.REFRESH_ACTION");
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(refreshReceiver, filter);
}
#Override
public void onDestroyView() {
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(refreshReceiver);
super.onDestroyView();
}
protected abstract void refresh();
}
In your Service, when your work is done, broadcast an intent with above action. So if there are fragments to show updated data, their receiver will be notified and that will call refresh ON EACH FRAGMENT. Something like from your service:
Intent intent = new Intent("package_name.REFRESH_ACTION");
LocalBroadcastManager.getInstance(MySyncService.this).sendBroadcast(intent);
The advantage is that you don't need to care when the fragment is shown or not since the receiver is there for the life-time of your fragment's view.
Recreate your adapter from scratch with the new data, and reassign it to the ListView (or whatever Views you have).
I have 2 views of essentially the same data:
List of items in a android.support.v4.app.ListFragment
Markers on a map in a com.google.android.gms.maps.SupportMapFragment
Both of the above are using loader pattern to obtain the same data (extending LoaderCallbacks, querying ContentProvider, and so on)
Both are hosted within a single activity inside a ViewPager.
What will be the best strategy to synchronize currently selected list item / marker for both of these fragments? (Think of "My Places" edit UI, or "Directions" of the Google Maps with their left-hand pane and a map in the center).
Scenarios i'm thinking of so far:
Make every fragment manually notify it's counterpart about selection change via callback interface (this will probably involve underlying activity to coordinate inter-fragment communications, as it is suggested by Android docs).
Somehow make both fragments use the same Cursor, or even ListAdapter (whatever it means for a map, because now it's populated directly from the cursor).
(Something else?)
Maybe someone has already dealt with this exact case? (I'll sure find some solution, just wanted to avoid "reinventing the wheel". Sorry for a too conceptual question.)
EDIT (Solution)
I think Maciej has answered my exact question ("best strategy", and so on..), so the answers are both 1 and 2 ;-)
Going into more details, my implementation went like this:
At first I frightened by enormous overhead of dealing with publisher/subscriber pattern in Java (involving interfaces, finding proper places for callbacks, and what's not). Fortunately, Otto bus implementation caught my eyes, which made communication between fragments a trivial thing. Not only it is possible to notify all subscribers about selection change, but also the whole Loader Patter fit nicely:
Borrow BusProvider class from Otto's sample code.
Create few message contracts to carry notification data:
public class LocationSelectedEvent {
public long id;
}
public class LocationsLoadedEvent {
public Cursor cursor;
}
Annotate "receiver" methods in fragments with #Subscribe (example below is for loader case, for selection change it's no more complex):
#Subscribe
public void onLoadFinished(LocationsLoadedEvent event) {
final CursorAdapter a = (CursorAdapter) getListAdapter();
a.swapCursor(event.cursor);
}
Make fragments "listening" to notifications:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
BusProvider.getInstance().register(this);
}
Make fragments to stop listening when they're not "alive" (specially true for fragments API, learned it the hard way):
#Override
public void onDestroy() {
super.onDestroy();
BusProvider.getInstance().unregister(this);
}
Finally, trigger notifications where desired (example below deomnstrates how to notify from LocationList activity when cursor has been loaded):
#Override
public void onResume() {
if(null == getLoaderManager().getLoader(0)) {
getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks<Cursor>() {
#Override
public Loader<Cursor> onCreateLoader(int paramInt, Bundle paramBundle) {
return new CursorLoader(LocationsList.this, Locations.CONTENT_URI, null, null, null, null);
}
#Override
public void onLoadFinished(Loader<Cursor> paramLoader, Cursor cursor) {
BusProvider.getInstance().post(new LocationsLoadedEvent(cursor));
}
#Override
public void onLoaderReset(Loader<Cursor> paramLoader) {
BusProvider.getInstance().post(new LocationsLoadedEvent(null));
}
});
}
super.onResume();
}
Bonus: notifications flow visualization
For click-coordination your point 1 is what I would do, using Activity and interfaces of course.
I just have hard time understanding why would you want to load the same data from ContentProvider twice. Why not load it once in a shared object? Some object inside Application, injected singleton or even another Fragment, which notifies Activity of data load complete and it pushes data to your two Fragments?
I have an activity which uses two Loaders. Each of them returns different type of data. To get data from a single Loader one just implements LoaderCallbacks<D> into an Activity. I guess I could just implement LoaderCallbacks<Object> and check the type of the object and then decide which of the two LoaderCallbacks it is, but it seems like a hack to me (mostly because of the lack of type safety here).
So I thought about making the LoaderCallbacks object a static inner class, something like this:
private static class geocoderLoaderCallbacks implements LoaderCallbacks<List<Address>>{
#Override
public Loader<List<Address>> onCreateLoader(int arg0, Bundle arg1) {
GeocoderTask loader = new GeocoderTask(context, "");
return loader;
}
#Override
public void onLoadFinished(Loader<List<Address>> loader, List<Address> data) {
// TODO Auto-generated method stub
}
#Override
public void onLoaderReset(Loader<List<Address>> loader) {
// TODO Auto-generated method stub
}
}
And then using lm.initLoader(0, null, geocoderLoaderCallbacks).
Two question arise: is it ok to do, or should I rather stick to implementing LoaderCallbacks into Activity? And how do I safely pass the context into the onCreateLoader? Should I just make a constructor in geocoderLoaderCallbacks and pass the context there like this lm.initLoader(0, null, geocoderLoaderCallbacks(this))?
There is a similar question here LoaderManager with multiple loaders: how to get the right cursorloader but it doesn't explain how to manage two loaders with different data types.
It is always ok to move code away from a potentially giant class and it's much cleaner to do it with different classes then with one that can handle everything. You might even want to make them real external classes instead of inner classes if you feel that your Activity has too much code inside. LoaderCallbacks is an interface so you can and mostly should implement it in it's own class.
Passing the Context in the constructor is fine as long as you don't keep static or otherwise cached references to it.
I'm using Android Compatibility Library in my project. I've set up ListFragment as described in the DevGuide (http://developer.android.com/reference/android/app/Fragment.html), and using a simple CursorLoader Christian made be used without content provider (CursorLoader usage without ContentProvider).
The question is where, in my ListFragment / parent Activity, should I open database, return the Cursor, create Adapter and setListAdapter?
So in my app, I have TitlesFragment, DetailsFragment, FragmentLayoutActivity, DetailsLayoutActivity.
Is the best practice...
to open database in ListFragment's onActivityCreatedand close it in ListFragment's onDestroy like in code sample below
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Open database
playersDatabaseHelper = new PlayersDBAdapter(getActivity());
playersDatabaseHelper.open();
getLoaderManager().initLoader(0, null, this);
...
}
#Override
public void onDestroy() {
super.onDestroy();
if (playersDatabaseHelper != null) {
playersDatabaseHelper.close();
}
}
query database and return the cursor in onCreateLoader, and create the Adapter and setListAdapter in onLoadFinished like in code sample below
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new MyCursorLoader(getActivity()) {
#Override
public Cursor loadInBackground() {
playersCursor = playersDatabaseHelper.getAllPlayers();
return playersCursor;
}
};
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Create an empty adapter we will use to display the loaded data.
playersAdapter = new RowAdapter(getActivity(), playersCursor, R.layout.players_overview_row);
// Allocate the adapter to the List displayed within this fragment.
setListAdapter(playersAdapter);
playersAdapter.swapCursor(cursor);
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
Am I on the right track or should I move some of those somewhere? Thanks for your time!
Sorry no experience in CursorLoader yet and Fragment, but I already experienced use of SQLiteOpenHelper in the context of concurrent access by different threads & activities.
I will assume that PlayersDBAdapter is internally using a SQLiteOpenHelper class. but it is not clear what your methods open() and close() are doing?
What I did:
define your SQLiteOpenHelper as an application wide singleton, not activity wide as you seem to do
instantiate SQLiteOpenHelper single instance in your Application onCreate
DON'T release SQLiteOpenHelper instance in any activity onDestroy, as when an activity stops, another one might still have to open the DB
I guess SQLiteOpenHelper instance should be cleared in application onTerminate (not sure as onTerminate is in practice almost never called)
I have DBAdapter object which gets a SQLiteDatabase reference with mySQLiteOpenHelper.getWritableDatabase()
these DBAdapter are typically allocated in activity onCreate and released in onDestroy
At least this works, no crashs in an application with several thousands users.
Suggestions to improve that are welcome :-)