I've created an Activity that contains a ViewPager with n number of Fragments. I have also added a Previous, Next and Finish buttons. Fragments are either multiple choice ListViews or just a single answer type.
How should I get the data from each fragment when the user hits Finish? Should I get each answer on the onPageSelected event from the ViewPager?
Communications between a Fragment and the activity that contains it is done via Interfaces.
The fragment should expose an interface listener and the activity should implement it and get notified by the fragment once an event happens.
You can use my example, and you can also change so the activity doesn't implement, but you can create the listener in run time anonymously (like you do for buttons on click listener many times).
class MyFragment {
// Container Activity must implement this interface
public interface OnNextPageListener {
public void onNextPageSelected(String DataString);
}
// define listener in fragment
OnNextPageListener mNextPageListener;
.... class code
#Override
public void onAttach(Activity activity) {
try {
mNextPageListener = (OnNextPageListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnNextPageListener");
}
.... class code...
...button press onclick listener method...
mNextPageListener.onNextPageSelected("My Name is Slim Shaddy");
}
class MainActivity extends Activity implements MyFragment.OnNextPageListener
public void onNextPageSelected(String StringData) {
}
...
read more here http://developer.android.com/training/basics/fragments/communicating.html
Either way I suggest you do this when someone pressed the button in the fragment, or hooking the ViewPager.OnPageChangeListener, starting the saving the data on onPageScrollStateChanged and completing it on the OnPageSelected so you ensure you have the data even if the fragment is not available anymore.
Related
I have an activity which calls another fragmentA .Now this fragmentA calls another fragmentB .Now I want to transfer data from fragmentB to my activity
check this: Communicating with Other Fragments
Define an Interface (In fragment)
To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity. The Fragment captures the interface implementation during its onAttach() lifecycle method and can then call the Interface methods in order to communicate with the Activity.
Here is an example of Fragment to Activity communication:
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
#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 = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
...
}
Now the fragment can deliver messages to the activity by calling the onArticleSelected() method (or other methods in the interface) using the mCallback instance of the OnHeadlineSelectedListener interface.
For example, the following method in the fragment is called when the user clicks on a list item. The fragment uses the callback interface to deliver the event to the parent activity.
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Send the event to the host activity
mCallback.onArticleSelected(position);
}
Implement the Interface (in activity)
In order to receive event callbacks from the fragment, the activity that hosts it must implement the interface defined in the fragment class.
For example, the following activity implements the interface from the above example.
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
}
}
PS1: EventBus works for you, but use it carefully if you need, it may make your code harder to read.
PS2: Don't pass an activity instance in Fragment.newInstance() and communicate using it. The activity instance may be destroyed in background. Get activity instance in Fragment.onAttach() like the example, the framework will handle the destroy & recreate & rebind for you.
You can get the Activity instance by overriding onAttach() method
Method: 1
#Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
this.activity=(ActivityName) activity;
}
and
activity.setdata(yourdata);
or
Method : 2
((ActivityName)getActivity()).setdata(yourdata);
In these ways you need to create a setter method in your activity
It is easiest way to get callback from any fragment to its parent activity .Very well explained by Dev Doc here
You can get the instance of the activity by calling getActivity() in fragmentB and pass the data using an interface.
You could use EventBus if you think that, you will need a similar functionality in different parts of the app and you don't want to write many interfaces for this porpoise.
You could use https://github.com/greenrobot/EventBus
Example:
Add compile 'de.greenrobot:eventbus:2.4.0',
Register on activities
OnCreate -
EventBus.getDefault().register(this);
and OnDestroy-
EventBus.getDefault().unregister(this);
Than add a method with a receiving object parameter and with onEventMainThread name:
public void onEventMainThread(YourObject name) {...}
Now from any Fragament you can call
EventBus.getDefault().post(yourObjectInstance);
And activity will detect it.
Or you could use RxJava to get similar effect -
http://nerds.weddingpartyapp.com/tech/2014/12/24/implementing-an-event-bus-with-rxjava-rxbus/
I'm loading data in the Activity that is displayed across all fragments.
The Activity sends them data via an "update" method the Fragments implement so they can populate the views.
Where can I safely call "update" from the Activity? (after all Fragments have finished "onCreateView")
Your design is backwards. You should create Fragments in a way where once created, they can then call a method on your Activity in order to update the View content. According to the Fragment Lifecycle, onCreateView is called after onAttach, meaning you have access to your Activity via the getActivity() method. Additionally according to the documentation, you should have your Activity implement an interface in order to respond to a method called from a Fragment. For example, create the interface OnFragmentCreatedListener:
public interface OnFragmentCreatedListener {
public void onFragmentCreated(Fragment fragment);
}
Then, implement this method in your Activity and add the following method:
public void onFragmentCreated(Fragment fragment) {
//TODO handle view creation
}
Finally, in each of your Fragments' onCreateView methods, add the following:
try {
((OnFragmentCreatedListener) getActivity()).onFragmentCreated(this);
} catch (ClassCastException e) {
Log.e(TAG, "Activity must inherit from interface OnFragmentCreatedListener", e);
}
Description of what I'm trying to accomplish:
I have an app that uses a FragmentActivity with a LinearLayout as a container for the fragments. I click different buttons on the FragmentActivity UI and add and remove Fragments to the container in the FragmentActivity. In addition to clicking buttons on the FragmentActivity UI, each Fragment also has buttons that can be clicked which will remove the current fragment and add a different fragment in its place.
The Android way of doing things as I understand it:
I have been reading up on how to do this and as I understand it, the 'proper' way of doing things is to use the FragmentActivity as sort of a relay station and have each fragment do callbacks to the FragmentActivity to communicate events and deal with them.
Scenario:
So let's say that the FragmentActivity is displaying Fragment A and when the user clicks a button in FragmentA I want to stop showing FragmentA and start showing FragmentB. To do this I have created an interface in FragmentA called AListener. In the onAttach() method of FragmentA I use the suggested method of checking that the FragmentActivity implements AListener. When the button in FragmentA is clicked I use one of the callback methods from AListener to communicate the click event to the FragmentActivity. In the FragmentActivity I create an instance of FragmentB and add it to the container in FragmentActivity. Then if some event happens in FragmentB I use the same scheme to communicate the event to the FragmentActivity and do something interesting.
So what's the problem?
For my application I have found this scheme of having Fragments call back to the FragmentActivity and then having the FragmentActivity create a new fragment or call forward to and existing fragment very cumbersome. I have many fragments that need to be displayed by the FragmentActivity and therefore I am implementing an interface for every type of fragment that needs to be displayed (Each fragment is different so they each have their own interface). This causes clashes when I have two interfaces that have the same method signatures and I'm forced to rename one of the methods.
For instance, if I want to attach a listener to a fragment using the onAttach() method of the fragment, then my FragmentActivity must implement the interface. I have found several instances where I have callback methods that have the same name (or I'm forced to name them something similar but different because of a namespace collision). One solution to this would be to use an anonymous classes as callbacks instead of having the FragmentActivity implement the interface. This seems to work well enough, but goes against what the Android documentation says about using the onAttach() method to set the listener.
Are there any elegant ways to approach this problem? It seems to me the tradeoff is that you either force the FragmentActivity to implement an interface for each Fragment that you want to display in it and have the fun problem of watching out for method signature collisions, or you go against the Android documentation and use Anonymous classes to handle the callbacks (not sure of the implications of this).
I am fairly new to Java and feel like I could be missing a concept here that would solve my problem. Can anyone set me straight on how to solve this problem elegantly?
I completely understand your problem since i was dealing it for a long time. Here is the solution i came up right now! It may need some modification based on your need but i it works well.
first of all to to make communicating of event easier in your app use an EventBus! here is the most famous one https://goo.gl/nAEW6
event bus allows you to send event from anywhere to anywhere without need to worry about implementing interfaces, broadcast receivers, threading, etc.
Then add FragmentOrganizer to your app. It's a base class for all of your Fragment Organizers. basically you need one for each activity. Here is the code
public abstract class FragmentOrganizer {
protected FragmentManager fragmentManager;
public FragmentOrganizer(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
openFragment(getInitialFragment());
EventBus.getDefault().register(this);
}
protected abstract Fragment getInitialFragment();
protected abstract void onEvent(Object event);
public abstract boolean handleBackNavigation();
public void freeUpResources(){
EventBus.getDefault().unregister(this);
}
protected Fragment getOpenFragment(){
String tag = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() -1).getName();
return fragmentManager.findFragmentByTag(tag);
}
protected boolean isFragmentOpen(Fragment fragment){
return isFragmentOpen(fragment, true);
}
protected boolean isFragmentOpen(Fragment fragment, boolean useArgs){
String fragmentTag = createFragmentTag(fragment, useArgs);
if (fragmentManager.getBackStackEntryCount() != 0) {
String name = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1).getName();
if(!useArgs)
name = name.substring(0, name.indexOf("-"));
return name.equals(fragmentTag);
}
return false;
}
private String createFragmentTag(Fragment fragment, boolean addArgs) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(fragment.getClass().getSimpleName());
if(addArgs) {
stringBuilder.append("-");
if (fragment.getArguments() != null)
stringBuilder.append(fragment.getArguments().toString());
}
return stringBuilder.toString();
}
public void openFragment(Fragment fragment) {
if(isFragmentOpen(fragment))
return;
String fragmentTag = createFragmentTag(fragment, true);
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.activity_main_fragment_container, fragment, fragmentTag);
transaction.addToBackStack(fragmentTag).commit();
}
}
Now you need to create your fragment organizer that inherit from FragmentOrganizer and implements 3 required methods. here the sample
public class MainFragmentOrganizer extends FragmentOrganizer {
public MainFragmentOrganizer(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
protected Fragment getInitialFragment() {
return HomeFragment.newInstance();
}
#Override
public void onEvent(Object event){
if(event instanceof ClickedOnPhotoEvent){
String photoCode = ((ClickedOnPhotoEvent) event).photoCode;
openFragment(PhotoFragment.newInstance(photoCode));
}
}
#Override
public boolean handleBackNavigation(){
Fragment fragment = getOpenFragment();
if (fragment instanceof HomeFragment){
return false;
} else {
fragmentManager.popBackStack();
return true;
}
}
}
And in your activity you just need to insatiate your FragmentManager and let it do the magic!
fragmentManager = getSupportFragmentManager();
fragmentOrganizer = new MainFragmentOrganizer(getSupportFragmentManager());
#Override
public void onBackPressed() {
//first let fragment organizer handle back. If it does not activity takes cares of it!
if(!fragmentOrganizer.handleBackNavigation()){
finish();
}
}
#Override
protected void onDestroy() {
fragmentOrganizer.freeUpResources();
super.onDestroy();
}
It may seem a lot of code but as you see most of the code encapsulated in FragmentOrganizer base class and it does all the general works so you just have to copy this file from one project to another.
As i said in the beginning i just came up with this solution right now, so it may not be perfect. I Plan to use this in my next project i hope you do to. And if you do i really appritiate if you share your though. have a good time
A co-worker of mine came up with what I consider an elegant solution to this problem.
Remember, what we're trying to achieve is a way for fragments to callback to the parent activity without having the activity implement the interface. Also, we need to be able to automatically set the listener again if the activity is destroyed and then recreated.
Activities have a lifecycle callback called onAttachFragment(Fragment fragment) which is called whenever a fragment is being attached to the activity. So, for instance, when a new fragment is created within the activity, this gets called. It also gets called if an activity that was previously destroyed gets recreated. What you can do is use an interface or an anonymous class to set a listener on the new fragment in onAttachFragment like this:
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
//Determine which fragment this is by checking its tag
if(fragment.getTag().contains(TextFrag.FRAG_TAG)){
//set a listener on this fragment using an anonymous class or interface
((TextFrag)fragment).setListener(new TextFragButtonListener() {
#Override
public void onButtonClicked() {
count++;
counterTV.setText(String.valueOf(count));
}
});
}
}
Using this technique we are able to avoid the activity having to implement an interface for the callback and thus we avoid any naming conflicts with our callback methods. Also, if the activity is destroyed, once it is recreated the listener will be automatically reset so our callbacks will still work.
There are probably many other ways to do this and I'd love to here anyone's criticisms of this technique and suggestions for any other techniques.
I have two Fragments in my Activity: fragment A with button X and fragment B with button Y.
How can I change button X's background image when I click button B? Is it possible?
From the documentation,
Because each fragment defines its own layout and its own behavior with its own lifecycle callbacks, you can include one fragment in multiple activities, so you should design for reuse and avoid directly manipulating one fragment from another fragment.
That being said, what you want to do is create event callbacks to the activity. A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary. This is the recommended way to share events between two separate Fragments--that is, sharing the event through the activity.
Check out the link above... it provides a couple nice examples. If you are still having trouble, let me know and maybe I can be more explicit.
Edit #1:
Let's say you click a button in fragment A and you want this to cause changes to a button in fragment B. Here's some sample code illustrating the concept:
The callback interface:
public interface OnButtonClickedListener {
public void onButtonClicked();
}
The activity:
public class SampleActivity extends Activity implements OnButtonClickedListener {
/* Implementation goes here */
public void onButtonClicked() {
// This method is called from fragment A, and when it is called,
// it will send information to fragment B. Remember to first
// check to see if fragment B is non-null.
/* Make call to a method in fragment B that will update its display */
}
}
Fragment A:
public class FragmentA extends Fragment {
OnButtonClickedListener mListener;
/* Implementation goes here */
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnButtonClickedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnButtonClickedListener ");
}
}
public void clickButton() {
// When the button is clicked, notify the activity.
// The activity will then pass the information to fragment
// B (if it has been created).
mListener.onButtonClicked();
}
}
Edit #2:
Now, you might be wondering, "Why would anyone ever go through all of this trouble? What's the point of creating a separate activity callback method when you could just have fragment A directly manipulate fragment B?"
The main reason you want to do this is to ensure that each fragment is designed as a modular and reusable activity component. This is especially important because a modular fragment allows you to change your fragment combinations for different screen sizes. When designing your application to support both tablets and handsets, you can reuse your fragments in different layout configurations to optimize the user experience based on the available screen space. For example, on a handset, it might be necessary to separate fragments to provide a single-pane UI when more than one cannot fit within the same activity. Making use of activity callbacks ensures that you will easily be able to reuse your fragments in situations where fragment B is not visible on the screen. For example, if you are on a handheld device and there is not enough room to display fragment B, then you can easily have your activity check to see if fragment B is currently being shown on the screen.
Sorry if this isn't clear... I'm finding it difficult to describe :P. Working your way through this tutorial might help... Activity callbacks make your life especially easier as a developer when you are working with interactive multi-pane layouts.
Base on Alex Lockwood's answer:
The activity:
public class SampleActivity extends Activity{
public interface OnButtonClickedListener {
public void onButtonClicked();
}
private OnButtonClickedListener onButtonClickedListener = null;
public OnButtonClickedListener getOnButtonClickedListener () {
return onButtonClickedListener
}
public void setOnButtonClickedListener (
OnButtonClickedListener onButtonClickedListener {
this.onButtonClickedListener = onButtonClickedListener;
}
}
Fragment A:
public class FragmentA extends Fragment {
private OnButtonClickedListener onButtonClickedListener = null;
private OnClickListener actionBarClickListener = new OnClickListener() {
#Override
public void onClick(View view) {
if (onButtonClickedListener == null){
onButtonClickedListener = ((SampleActivity) getActivity()).onButtonClickedListener ();
}
if (onButtonClickedListener != null) {
onButtonClickedListener
.onButtonClicked();
}
}
};
}
Fragment B:
public class FragmentB extends Fragment {
private OnButtonClickedListener onButtonClickedListener = new OnButtonClickedListener() {
#Override
public void onButtonClicked() {
Toast.makeText(getActivity(), "Button clicked", Toast.LENGTH_SHORT).show();
}
};
#Override
public void onResume() {
super.onResume();
SampleActivity sampleActivity = (SampleActivity) getActivity();
sampleActivity.setSearchBoxTextChangedListener(onButtonClickedListener);
}
}
Hope can help someone.
Setting the onClick attribute for a button in your layout, even your fragment's layout, will call the appropriate method on your Activity.
Your app can then send this signal from your Activity to fragment B.
I'm converting some of my project to use fragments. How do we communicate with a fragment dialog? I want to create a fragment dialog just to get some text input from the user. When the dialog is dismissed, I'd like to pass the entered text back to the "parent" fragment (the one that started it). Example:
public class MyFragment extends Fragment {
public void onBtnClick() {
// What's a good way to get data back from this dialog
// once it's dismissed?
DialogFragment dlgFrag = MyFragmentDialog.newInstance();
dlgFrag.show(getFragmentManager(), "dialog");
}
}
Thanks
As eternalmatt said the given solution does not really answer the question. The way to communicate the dialog with the fragment is calling:
dialog.setTargetFragment(myCallingFragment, requestCode);
The way I do this is by creating the FragmentDialog with an static method where the listener is instanciated an then do the setFragmentTarget() stuff:
public mySuperFragmentDialog extends DialogFragment {
public interface SuperListener{
void onSomethingHappened();
}
public static mySuperFragmentDialog newInstance(SuperListener listener){
MySuperFagmentDialog f = new MySuperFragmentDialog();
f.setTargetFragment((Fragment) listener, /*requestCode*/ 1234);
return f;
}
}
To create the dialog from the fragment just do as usual:
Dialog dialog = MySuperFragmentDialog.newInstance(parentFragment);
dialog.show();
Then when you want to comunicate with the fragment which calls the dialog just:
Fragment parentFragment = getTargetFragment();
((SuperListener) parentFragment).onSomethingHappened();
This solution works only when dialog is gonna be created from Fragments and not from Activities, but you can combine both methods ('setFragmentTarget()' and the 'onAttach()' one) plus some Class checks to provide a full solution.
A great way to pass this kind of Events is a Callback Interface like descripted in the Android Developers Guide
Your Fragment define a Callback Interface like
public class MyFragment extends Fragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
Then you check inside your onAttach Method if the Parent implemented the Callback Interface and save the Instance.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
when your Event inside the Fragment happens you simply call the Callback Handler
mListener.onArticleSelected(...);
Hope that helps, further infos here
I had this problem once and after I solved it, I created a project that would remind me how I did it. I put the project on github so anyone can see the solution. Here is the link: https://github.com/mumasaba/FragmentFragmentBoss
In this project, we have a simple app with a TextView displaying the words 'Hello World'. This text view is on a fragment which is hosted by the main app activity. This fragment needs to display a new word that the user can enter after they click on the add options menu icon. When clicked, the options menu item calls up a dialog allowing the user to type in their new word. After the user is done, they can click ok to dismiss the dialog and display their new input on the fragment's text view. Therefore, Fragment to DialogFragment communication is illustrated.
There is a new pattern possible which is to share a ViewModel instance between fragments. When instantiating a ViewModelFactory where to get your ViewModels, you have to specify a context as parameter. If the context is the same for both fragments (i.e: the parent activity or parent fragment) and you instantiate the same ViewModel from both fragments, you will get the same instance.
This opens a new range of possibilities but also challenges.