Okay, so I have a MainActivity.java which has a Fragment, called FavoritesFragment.java, at some point I call a MyListFragment.java where people are supposed to choose a list element.
After that I want to get their choice back in my FavoritesFragment.java.
So I implemented an interface for that. In MyListFragment, a method is called, that sets settings in FavoritesFragment:
From MyListFragment:
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Station s = (Station) l.getItemAtPosition(position);
updater.stationChosen(s, isFrom);
getFragmentManager().popBackStack();
}
Where updater is my interface.
So, everything works fine, my Content in FavoritesFragment is updated and everything. But after I call popBackStack() everything in FavoritesFragmentreset to former glory.
When I used to do everything with Activities there was this onActivityResult(...) callback from which you could exchange data. How can I do this with fragments? I don't want to use Activities for this minor case, and I want to have the list in it's own class, because I'm going to need it in other classes.
You need some place to keep track of favorites that is outside of the Fragments. One place is in the Activity that hosts the Fragments, which you can access via getActivity() and casting it to MainActivity or some interface that you define.
For example:
public interface FavoriteManager {
void onFavoriteAdded(Station s);
void onFavoriteRemoved(Station s);
List<Station> getFavorites();
etc.
}
public class MainActivity extends FragmentActivity implements FavoriteManager {
...
private List<Station> mFavorites;
...
#Override
public void onFavoriteAdded(Station s) {
mFavorites.add(s);
}
...
#Override
public List<Station> getFavorites() {
return mFavorites;
}
}
public class FavoritesFragment extends Fragment {
...
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Station s = (Station) l.getItemAtPosition(position);
FavoriteManager manager = (FavoriteManager) getActivity();
manager.onFavoriteAdded(s);
getFragmentManager().popBackStack();
}
...
}
public class MyListFragment extends Fragment {
...
#Override
public void onStart() {
List<Station> favorites = ((FavoriteManager) getActivity()).getFavorites();
populateUi(favorites);
}
...
}
Another option is to use a database and a ContentProvider, as shown here: http://developer.android.com/guide/topics/providers/content-providers.html
Perhaps it's better to think about the fragments existing simultaneously than one after the other (like activities would). You aren't calling a Fragment and getting a result, but calling the fragment and allowing it to change data.
I would think the easiest thing to use the Observer pattern to have your FavoritesFragment observe changes on the underlying data, and make sure that the update from MyListFragment calls notifyobservers(). Then when FavoritesFragment comes back to the front, it'll notice the change
Related
PlaylistFragment starts an adapter:
playlistsAdapter = new PlaylistRecyclerAdapter(playlistsListArray, addToPlaylist, mSong, getActivity(), this);
PlaylistRecyclerAdapter binds data to the PlaylistViewHolder, something like this:
((PlaylistViewHolder) viewHolder).bind(this, dataSet.get(position), addToPlaylist, mSong);
User clicks on an item in PlaylistViewHolder:
context.startActivity(PublicPlaylistActivity.createStartIntent(context, playlist));
Now here is the question, how can PublicPlaylistActivity talk back to the initial PlaylistFragment?
I suggest you'd better use Interface from fragment to adapter. So when user clicks anything in adapter, call function realization in fragment. If you need your activity to proceed some operation - ((YourActivity) getActivity()).someMethod() should be called from fragment.
Second trick is using broadcastreceiver to send events. A bit more complicated. You have to launch broadcast in view you need to recive message and send these messages from adapter. This approach is more complexible to debug and support if system is wide spread, so you'd better use interfaces.
There are several ways of doing that. The simplest way should be starting the PublicPlaylistActivity with startActivityForResult. In that way, then the activity finishes, you can set send some data to the caller fragment (which is PlaylistFragment in your case). Here is a nice tutorial about the implementation.
Another way of doing that is by using lifecycle methods. You might have a public static variable which can keep track of some status that you might observe in your onResume function of your PlaylistFragment when you are returning back from your PublicPlaylistActivity. You might consider a sample implementation as follows.
Define a public static variable in your PlaylistFragment. Then in your onResume function check the value of that variable and take actions accordingly.
public static boolean someIndicator = false; // Initialize with a default value
#Override
protected void onResume() {
super.onResume();
if(someIndicator == true) doSomething();
else doSomethingElse();
}
Now you can set the indicator variable from anywhere in your application actually which will have the effect on your PlaylistFragment. For example, from your PublicPlaylistActivity, you might consider doing something like this.
public void someFunctionInYourPublicPlaylistActivity() {
// ...
// Some code and then the following
PlaylistFragment.someIndicator = true;
}
Another way of achieving the same thing is by using a BroadcastReceiver. Here is a tutorial on how you can implement one.
It really depends on how you are structuring your whole activity-fragments communication. Hope that helps!
I would do a common "context" class (ComContext) with an interface. When you create your fragment, you also create this class. And from the activity you can check if it exists or not.
I assume that you already have a helper(AppHelper) class with static variables.
public class AppHelper {
public static ComContext comContext = null;
}
public class MainFragment {
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ConContext comContext = new ComContext();
comContext.listener = this;
AppHelper.comContext = comContext;
}
#Override
public void onDataChanged() {
}
#Override
public void onDestroyView() {
super.onDestroyView();
AppHelper.comContext = null;
}
}
public class MainActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (AppHelper.comContext != null) {
AppHelper.comContext.listener.onDataChanged();
}
}
}
public class ComContext {
public interface HelperListener {
void onDataChanged();
}
public HelperListener listener = null;
}
I have a RecyclerView which items' contain a button that launches an activity using startActivityForResult. The onClick method was implemented inside the adapter, and onActivityResult from the fragment that contains the RecyclerView was not called upon returning from the new activity. As I learned, this is to be expected, because startActivityForResult was not called from this fragment.
The closest thing to a solution I found was this:
onActivityResult inside a RecyclerView.Adapter not being used
But when trying to use an interface to implement the onClick method, I now face a new problem: I can't call the onClick method inside the adapter, although it's implemented inside the activity, because the Adapter's context is static, and the method is non-static.
Below are some code portions to explain better the current situations. I'm in desperate need for an alternative solution, I really searched through and through. Thanks.
The fragment implements the interface:
public class EditableOffersListFragment extends Fragment implements OnClickButtonListener { ...
#Override
public void onClickButton(View v, int position, ArrayList<Offer> offers) {
Offer from_item = offers.get(position);
Intent intent = new Intent(getActivity(), OfferDetailsPopupActivity.class);
intent.putExtra("new", false);
intent.putExtra("offer_fromRecycler", from_item);
getActivity().startActivityForResult(intent, HTZ_ADD_OFFER);
}
...}
The interface (in a seperate file):
public interface OnClickButtonListener {
void onClickButton(View v, int position, ArrayList<Offer> offers);
}
And inside the adapter:
mEditOfferButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
OnClickButtonListener.onClickButton(v, getAdapterPosition(), mOffers);
}
});
I think the problem you're currently facing is that you're calling startActivityForResult from the Activity containing the current fragment rather than the Fragment itself. So using this or just plainly calling the startActivityForResult will call it from the Fragment instead of the Activity and this fragment will be callbacked with the result from the other Activity.
public class EditableOffersListFragment extends Fragment implements OnClickButtonListener { ...
#Override
public void onClickButton(View v, int position, ArrayList<Offer> offers) {
Offer from_item = offers.get(position);
Intent intent = new Intent(getActivity(), OfferDetailsPopupActivity.class);
intent.putExtra("new", false);
intent.putExtra("offer_fromRecycler", from_item);
startActivityForResult(intent, HTZ_ADD_OFFER); // <--- Note that this line has changed
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Do your result handling here
}
}
Ok, finally solved it.
What worked was adding a OnClickButtonListener member to the Adapter, which is passed from the Fragment in the Adapter's constructor (the Fragment is implementing OnClickButtonListener so I just passed 'this' to the constructor).
That way, all of the actions performed in the adapter are on an instance of OnClickButtonListener and the static/non-static problem was solved.
I'm using Android Studio. I haven't been able to find an answer online, so even a link to a solution would be helpful.
I have an Activity, which includes a number of Fragments. One of these Fragments is called BookGridFragment, which uses a class called BookGrid.
BookGridFragment looks like this (I've left out irrelevant bits):
public class BookGridFragment extends Fragment {
BookGrid myBookGrid;
public BookGridFragment() {}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// Inflate layout
View rootView = inflater.inflate(
R.layout.fragment_book_grid, container, false);
myBookGrid = rootView.findViewById(book_grid);
return rootView;
}
public void setBook(Book thisBook) {
myBookGrid.setBook(thisBook);
}
}
The BookGrid class is:
public class BookGrid extends View {
private Book mBook;
public BookGrid(Context thisContext, AttributeSet attrs) {
super(thisContext, attrs);
}
public void setBook(Book newBook) {
mBook = newBook;
}
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
}
public boolean onTouchEvent(MotionEvent event) {
// This function responds to the user tapping a piece of
// book info within the grid
// THIS IS WHERE I'M HAVING PROBLEMS
}
}
So, that all works fine. The issue is, that I need the BookGridFragment to know when the user touches the BookGrid and to pass that information to another Fragment (via the Activity). So, I assume that when the onTouchEvent is reached, that should somehow notify the BookGridFragment that the BookGrid was touched, but I can't figure out how to do that.
Everything I've found online is about passing information between Fragments, but that approach doesn't work here as the BookGrid class doesn't "know" that it's within a BookGridFragment.
You could use the same idea that is used to communicate a Fragment and an Activity. Create an interface:
public interface OnBookGridTouched{
void onTouchGrid();
}
Add a variable to your BookGrid:
private OnBookGridTouched mCallback;
Add a setter to this variable:
public void setCallback(OnBookGridTouched callback){
mCallback = callback;
}
Then make your fragment implement the interface:
public class BookGridFragment extends Fragment implements OnBookGridTouched {
You'll be forced to implement the method onTouchGrid
In your fragment onCreateView pass the fragment to your custom view:
myBookGrid.setCallback(this);
Finally, in your custom view you can call the callback to reference the fragment:
public boolean onTouchEvent(MotionEvent event) {
// This function responds to the user tapping a piece of
// book info within the grid
// THIS IS WHERE I'M HAVING PROBLEMS
mCallback.onTouchGrid();
}
A solution could be to set the onTouch/onClick listener in the fragment instead of in the BookGrid itself. From there you can use the fragment method getActivity() to call an activity method, parsing on the correct data to the correct fragment.
I think this situation is very similar to a Fragment containing a Button.
The Button has a method which accepts something implementing a certain interface (for the Button: View.OnClickListener). The Fragment calls that method (for the Button: setOnClickListener()) to pass in the desired Object implementing all the required methods, either an anonymous class or maybe a field or the Fragment itself. There are pros and cons for all three approaches, it depends on your situation which one is best.
They have in common that BookGrid should have an interface as well as a method so other classes can set the current Object implementing that interface.
I am not quite sure about the exact scenario that you are having there. However, if the problem is the communication between the fragment and an activity which hosts the fragment, then you might think of the following implementation.
Let me point out some of your concerns first.
Everything I've found online is about passing information between
Fragments, but that approach doesn't work here as the BookGrid class
doesn't "know" that it's within a BookGridFragment.
BookGrid class will know the context of its existence when you will pass the Context towards it while calling a function of it. So I would like to suggest passing the context of the Activity or Fragment when you are calling a function from your BookGrid class.
public class BookGrid extends View {
private Book mBook;
private Context context;
public BookGrid(Context thisContext, AttributeSet attrs) {
super(thisContext, attrs);
this.context = thisContext;
}
public void setBook(Book newBook) {
mBook = newBook;
}
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
}
public boolean onTouchEvent(MotionEvent event) {
// Call the function of your host activity
((YourActivity)(thisContext)).onBookGridTouched();
}
}
Now write a public method in your activity class which hosts the fragment named onBookGridTouched.
public void onBookGridTouched() {
// Communicate with other fragments here
}
However, a noble approach of solving this problem in a more generic way is to use an interface and then implement the interface wherever necessary like #LeviAlbuquerque suggested.
I am just putting another workaround which is a bit static.
Assuming that you have ONE Activity responsible of all fragments:
1.Create an interface in your BookGrid:
public interface ActionHappened {
void onActionHappened();
}
2.Create an instance of your interface within your BookGrid class and trigger the method onActionHappened where you want it to be triggered. For instance, if you would like it to happen in your onDraw(), then do the following:
ActionHappened actionHappened;
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
actionHappened.onActionHappened();
}
3.Implement your interface within your activity
public class ActivityA extends AppCompatActivity implements BookGrid.ActionHappened {}
4.Within your implemented method, trigger the method:
#Override
public void onActionHappened() {
Fragment fragmentA = getSupportFragmentManager().findFragmentByTag(R.id.fragmentA);
Fragment fragmentB = getSupportFragmentManager().findFragmentByTag(R.id.fragmentB);
//Trigger that method from your activity to fragmentA or fragmentB
fragmentA.doWork();
fragmentB.doWork();
}
Wether you would like to pass data to fragmentA or fragmentB, doWork() method will do that for you. Make you create such a method in the corresponding fragment.
I am confused about how communication with a Fragment and an Activity is made. For example, an interface was defined here (https://developer.android.com/training/basics/fragments/communicating.html).
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
...
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// The user clicked on a list item.
mCallback.onArticleSelected(position);
}
}
Eventually, the following method is called from the MainActivity.
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// Do something
}
}
My questions are:
How does mCallback "know" which onArticleSelected method to call (as there might be other classes that have implemented OnHeadlineSelectedListener).
mCallback.onArticleSelected(position);
I wouldn't be confused if it went:
mCallback = new OnHeadSelectedListener() {
#Override
public void onArticleSelected(int position)
// Do something
}
and then mCallback is referred in some way in MainActivity to utilize the onArticleSelected method in some way. In the example code, however, the line intelligently sticks to "an" interface. How does that happen?
Also, I found that the Log I implemented onArticleSelected method from MainActivity is called previously to the one in onListItemClick method in HeadlineFragment. Is it expected?
Since you defined the OnHeadlineSelectedListener interface with only one method, and your activity implements it, there's no ambiguity in choosing the method when you use the activity as instance of this interface, 'cause all you know about activity while using it as instance of OnHeadlineSelectedListener interface is a presence of onArticleSelected(int) method in it.
It depends on when you call the logging function - before or after calling the callback method.
P.S. While this kind of communication between Activity and Fragment (or any other objects) is perfectly fine, personally I prefer the Event Bus approach, 'cause it gives us a possibility to organize code in a low coupled manner. Here are some nice implementations of Event Bus pattern:
https://github.com/greenrobot/EventBus
http://square.github.io/otto/
Take a look at them if you are interested in this approach.
Your mCallback is your activity, in the onAttach method of your fragment, you will set the activity as listener for your fragment. By this way, this is normal that the activity is notified when you call mCallback.onArticleSelected(position);
For your first part of question
You should have a look on onAttach and onDetach methods-
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallbacks = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement OnHeadlineSelectedListener.");
}
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
Second part-
The behavior is not expected.
You should not make your activity class static as well.
I am having a ViewPager to allow the user to slide between 5 different views where each "view" extends Fragment.
I have my own adapter which extend FragmentPagerAdapter and which implement getItem() as
#Override public Fragment getItem(int position) {
switch(position) {
case 0:
return new TextDescriptionFragment();
// Handle the 4 other cases the same way
}
}
This works fine and the user can swipe between the 5 different views. But here comes the problem: Each of the first 4 views contains Views such as Button and EditText which the user can interact with.
And I then want the last page(Page number 5) to show all the user input values from all the views from the 4 previous pages(fragments). How do I do that?
I can't find any way to read the user input values from the previous fragments. The views may not even exist anymore(But will be recreated if the user goes back).
And I can't seem to get the existing fragments.
I would consider having a custom object that keeps the data each fragment fills. Something like:
public class FillerData implements Parcelable {
private String page0$data0;
private String page0$data1;
private String page0$data2;
// getters and setters if you wish
// implement Parcelable interface as this object will be managed by host activity
}
You'll have only one such object managed by parent activity and the parent activity will implement an interface for exposing this object:
public static interface FillerDataExposer {
public FillerData exposeFiller();
}
public class MyFragmentHostActivity extends FragmentActivity implements FillerDataExposer {
private static final String FILLER_KEY = "FILLER_KEY";
private FillerData myFillerData;
protected void onCreate(Bundle savedInstance) {
.......
if(savedInstance != null) {
myFillerData = (FillerData) savedInstance.getParcelable(FILLER_KEY);
} else {
myFillerData = new FillerData();
}
}
protected void onSaveInstanceState(Bundle savedInstance) {
super.onSaveInstanceState();
savedInstance.putExtra(FILLER_KEY, myFillerData);
}
public FillerData exposeFiller() {
return this.myFillerData;
}
}
Now, each of your fragments will have access to that centralized data filler object through parent activity. To reduce the weight of your code, all your fragments could extend from a base fragment class that provides access to FillerDataExposer implementation (actually, the parent activity):
public abstract class AbstractFillerFragment extends Fragment {
protected FillerDataExposer dataExposer;
public void onAttach(Activity act) {
super.onAttach(act);
// make sure no ClassCastExceptions
this.dataExposer = (FillerDataExposer) act;
}
}
Fragments that should only record the data filled could look like this:
public class Page1Fragment extends AbstractFillerFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = // inflate XML resource ...
yourEditText = (EditText) view.findViewById(...);
// other relevant code ....
}
public void onViewCreated(View view, Bundle savedInstanceState) {
yourEditText.setText(dataExposer.exposeFiller.setPageX$DataY());
// some code for EditText.addTextChangedListener(new TextWatcher() could look like:
yourEditText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
dataExposer.exposeFiller().setPage1$Data0(s.toString());
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
}
}
While the fragment that needs to have access to all data stored could look like this:
public class FinalFragment extends AbstractFillerFragment {
public void collectAllData() {
DataFiller allDataCollectedObject = dataExposer.exposeFiller();
// by calling get...() you should have access to collected data.
}
}
This only a sketch, but you'll get the picture. The idea is to keep a single object in your activity managed across activity restarts and to make it accessible through interfaces so you will respect the fragment to activity patterns.
Hope it makes sense ...
2 solutions come to my mind.
The first one is to save user input data when your first 4 fragment's onpause() methods are called. You may save the data to a preference and then retrieve it from your 5th fragment.
The second approach is to persist your fragments while swiping.This way the swiping will be faster and cleaner and they want be recreated everytime a swipe happens:
yourcustomviewpager.setOffscreenPageLimit(5);
About setOffscreenPageLimit from the android doc:
Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number of pages you will need to support or have lazy-loading mechanisms in place on your pages, tweaking this setting can have benefits in perceived smoothness of paging animations and interaction. If you have a small number of pages (3-4) that you can keep active all at once, less time will be spent in layout for newly created view subtrees as the user pages back and forth.
You should keep this limit low, especially if your pages have complex layouts. This setting defaults to 1.