Validate fields on multiple Fragments hosted by a ViewPager - android

My app works with a long form which I decided to divide in multiple Fragments in a ViewPager. When you press the "save" option, the validation process starts.
Basically the validation is that some EditTexts are not empty. I'm looping through all Fragments in the ViewPager check if all fields has valid values.
// Inside Fragment
public boolean areFieldsValid() {
return !mEditText.getText().toString().isEmpty()
}
public void showErrors() {
mEditText.setError("cannot be blank");
}
If a field inside a Fragment is not valid, then viewPager.setCurrentItem(position, true); and fragment.showErrors() are called to go to that Fragment and show the user the error.
The problem comes when onCreateView() hasn't been called on the Fragment that has the error.
This happens either because you haven't navigated to that Fragment yet (supposing the user's on fragment1, error is on fragment7 and the user pressed "save" while on fragment1) or because the user rotated the device and all views are destroyed on every Fragment.
This problem/issue is not only that mEditText would be null, but also that the Fragment saved its state, so it might not even been blank. In other words, the following code is not an option, because even if the pointer is null, it might not be empty.
// Inside Fragment
public boolean areFieldsValid() {
return mEditText != null && !mEditText.getText().toString()isEmpty();
}
At this point I'm wondering if my architecture is wrong. I decided to go with ViewPager cause the form is really long, and I've been passing data from Fragment to Activity through callbacks.
Given the above settings, how can I validate fields and show the user which field is the one with the error?

You can't just assume that UI components will be there anytime you want. That fragment might be gone, killed or worse, destroyed without saving it's instance state.
What I offer is to save data on database and check if everything is set on save button click event. This can be done using ContentProviders and SQLiteDatabase. As Virgil Said in here "Persist more, persist often."

I have implemented a similar thing, but my approach is to go fragment by fragment. Hope this helps.
I add an interface,
public interface AddActionInterface {
public void onAddButtonClicked();
}
I created a base fragment which implements this interface as,
public abstract class BaseFragment extends Fragment implements AddActionInterface {
#Override
public void onAddButtonClicked() {
if (isAdded() && isVisible()) {
executeAction();
}
}
protected abstract void executeAction();
}
Then we will call our Interface object like this in the activity. Create a List like below,
List<AddActionInterface> listeners = new ArrayList<AddActionInterface>();
and add your fragment to the list inside the view pager as,
listeners.add(fragment);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment, tag).commit();
Simply call the below in the onOptionsItemSelected method.
if (item.getItemId() == R.id.action_add) {
for (AddActionInterface listener : listeners) {
listener.onAddButtonClicked();
}
}
What the above method does is calls the onAddButtonClicked() method which is implemented in the BaseFragment.
Trick here is that every time the button in the action bar is clicked it will pass the control to the BaseFragment which checks if the current fragment is still attached then will call the executeAction() method of the respective fragment which being abstract every fragment can have their own implementation.
So say for FragmentA you will simply have to extend it from BaseFragment and override executeAction() method. You can write fragment specific implementations.
This process is called dependency inversion principle. See if you can put all these pieces in right place else let me know. :) Wow this is huge. :)

On the viewpager class:
public void validate() {
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
Fragment fragment = mSectionsPagerAdapter.getItem(i);
if(!(fragment instanceof Validetable)) {
return;
}
Validetable validetable = (Validetable) mSectionsPagerAdapter.getItem(i);
Fragment invalidFragment = validetable.validate();
if (invalidFragment == null) {
Toast.makeText(getActivity(), "valido", Toast.LENGTH_LONG).show();
}
else {
mViewPager.setCurrentItem(i);
break;
}
}
On each fragment you do:
public static boolean isValid = true;
#Override
public Fragment validate() {
if ( StringUtils.isBlank(ColetaLocal.getInstance().getNivel())) {
isValid = false;
return this;
}
isValid = true;
return null;
}
#Override
public void onResume () {
super.onResume();
treatErrorsShowing();
}
private void treatErrorsShowing() {
if (!isValid) {
showErrors();
}
else {
clearErrors();
}
}

I ended up validating each Fragment before moving to the next one.
Reason:
The initial idea was to validate on save, and if there was an Fragment with invalid data, move to that fragment and show the errors. But since there is no way to determine the state of Views inside a Fragment if it is not visible, you cannot validate input.

Related

Save fragments states of a viewpager Activity after going to previous activity

Scenario:
Activity A opens Activity B, Activity B has two fragments a and b in a viewpager using FragmentPagerAdapter, fragments a and b has some radio buttons and check boxes where the user interacts with;
I need to store the buttons and checkboxes statuses in the fragments even if I left Activity B to A
My Attempts:
1- removed super.onBack() pressed to force the system into calling onSavedInstanceState in the activity, but still couldn't save fragments to it as am instantiating them in the adapter and do not know how to get the same object created of them to activity
2- tried the onSaveInstanceState(), onViewStateRestored() in each fragment and onSavedInstance never got called using setRetainInstance(true) in the oncreate(); then forced calling onSaveInstanceState() by calling it onPause()
I Read most of available solutions on stack and non of them worked, My Code is as follows;
public class FilterPagerAdapter extends FragmentPagerAdapter {
public final static int KEYWORDS_TAB = 0;
public final static int AREAS_TAB = 1;
private int tabCount;
private Context context;
public FilterPagerAdapter(FragmentManager fm, int tabCount, Context context) {
super(fm);
this.tabCount = tabCount;
this.context = context;
}
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case KEYWORDS_TAB:
fragment = KeyWordsFragment.newInstance();
break;
case AREAS_TAB:
fragment = AreasFragment.newInstance();
break;
default:
return null;
}
return fragment;
}
#Override
public int getCount() {
return tabCount;
}
#Override
public CharSequence getPageTitle(int position) {
String tabTitle = "";
switch (position) {
case KEYWORDS_TAB:
tabTitle = context.getResources().getString(R.string.tab_keywords);
break;
case AREAS_TAB:
tabTitle = context.getResources().getString(R.string.tab_area);
break;
default:
}
return tabTitle;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_filters);
ButterKnife.bind(this);
mAreas = new Areas();
mKeywords = new KeyWords();
viewControllers();
}
private void viewControllers() {
tabLayout.addTab(tabLayout.newTab().setText(R.string.tab_keywords), CATEGORIES_TAB);
tabLayout.addTab(tabLayout.newTab().setText(R.string.tab_area), BRANDS_TAB);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.addOnTabSelectedListener(this);
filterPagerAdapter = new FilterPagerAdapter(getSupportFragmentManager(),
tabLayout.getTabCount(), this);
viewPager.setAdapter(filterPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
}
#Override
public void onBackPressed() {
startActivity(new Intent(FiltersActivity.this, BranchesActivity.class));
}
#Override
public void onStop() {
super.onStop();
tabLayout.removeOnTabSelectedListener(this);
}
}
First off, I would recommend not forcing any call to onSavedInstanceState or any of the other lifecycle callbacks. It's better to fit to the flow of the lifecycle than to make the lifecycle fit your flow.
Second, there most likely isn't a reason to use setRetainInstance() on Fragments like this. This method is useful for creating viewless Fragments and store large data-sets. Going beyond that use-case tends to bring a lot of headaches.
So, with those out of the way, there should be a couple of ways to solve this, and they are dependent on the type of data it represents.
Method 1:
One way, which is similar to what you tried with #1, is advance to the next Activity (in this case ActivityA) rather than going back when the user presses back. This will pause and possibly stop ActivityB as well as use the onSaveInstanceState() method in which you can store the state of your Fragments.
The FragmentPagerAdapter will only call getItem() when it needs a new Fragment. All the Fragments that are already created will then be in the FragmentManager. Each Fragment will have to handle its own state. There's no need for the Activity or the Adapter to know about it. Each previously created Fragment will have onSavedInstanceState() called, and the bundle returned from that will be passed in onCreate(bundle) and onCreateView(bundle).
Method 2:
The above works for a lot of cases where the data is not very important. You could get away with it being wiped, and keeping the state is more for convenience. If the user was away from ActivityB for a while for whatever reason, the system could dump it from the stack. If that happens, then it could still be destroyed and the state reverts. In this case, you'll have to use SharedPreferences or some other more permanent data storage to save and restore the state.
Method 3:
I noticed that the name of ActivityB is actually FiltersActivity. I'm making the assumption that FiltersActivity is an Activity in which the user selects filters that get sent back to BranchesActivity. If you don't want to make these persistent, then another method then would be to simply pass the information back and forth between the two.
BranchesActivity will pass in the current filters to FiltersActivity.
FiltersActivity then defaults all its settings to the filters that were passed in.
FiltersActivity lets the user select the new set of filters.
The users presses "back" on FiltersActivity which returns the selected filters.
BranchesActivity now has new filters. When the user wants to change the filters, BranchesActivity will go back to step 1.

Get state of activity (paused / resumed)

I am using a LoaderManager to get some data and when it finishes a child fragment should be shown. In some cases this happens when the activity is already in paused state and can not perform the fragment transaction.
Is there a way to get the current state of the activity (seems to have a mResume flag)? Or do I have to maintain my own boolean?
The new Architecture Components allow you to do it with:
this.getLifecycle().getCurrentState()
A quick look in the Activity source code indicates that the Activity class does keep track on the resume state with the member mResumed. But since mResume is not public and isResumed() is hidden, we can't use them.
You can have a simple solution to provide you with that information for all your classes. Simply create a base Activity class that store the state. For example:
public class ActivityBase extends Activity {
private boolean mIsResumed = false;
#Override
public void onResume() {
super.onResume()
mIsResumed = true;
}
#Override
public void onPaused() {
super.onPaused()
mIsResumed = false;
}
public boolean isResumed() {
return mIsResumed
}
}
Simply extend this class with your class:
public class MyActivity extends ActivityBase {
private void onLoadDone() {
if (isResumed()) {
// Show the fragment
}
}
}
One way it could be achieved is by using breakpoints on your Activity (for instance, putting a breakpoint in your onResume method), and using the Evaluate Expression window that you can open by clicking a right click on your Debug menu window, and selecting it from there (OR SHIFT + F8) for mac. Once opened, you can intercept the current state (depending where your breakpoint is) using this line in your Evaluate Expression Window:
getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)
If it returns true, that means your Activity is currently in the resume state.
If false, it's in another state.
They have plenty of other states you can play with, just check here

Android managing fragments from activity elegantly

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.

Fragment's reference to mActivity becomes null after orientation change. Ineffective fragment state maintenance

My application consists of several fragments. Up until now I've had references to them stored in a custom Application object, but I am beginning to think that I'm doing something wrong.
My problems started when I realized that all my fragment's references to mActivity becomes null after an orientation change. So when I call getActivity() after an orientation change, a NullPointerException is thrown.
I have checked that my fragment's onAttach() is called before I make the call to getActivity(), but it still returns null.
The following is a stripped version of my MainActivity, which is the only activity in my application.
public class MainActivity extends BaseActivity implements OnItemClickListener,
OnBackStackChangedListener, OnSlidingMenuActionListener {
private ListView mSlidingMenuListView;
private SlidingMenu mSlidingMenu;
private boolean mMenuFragmentVisible;
private boolean mContentFragmentVisible;
private boolean mQuickAccessFragmentVisible;
private FragmentManager mManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*
* Boolean variables indicating which of the 3 fragment slots are visible at a given time
*/
mMenuFragmentVisible = findViewById(R.id.menuFragment) != null;
mContentFragmentVisible = findViewById(R.id.contentFragment) != null;
mQuickAccessFragmentVisible = findViewById(R.id.quickAccessFragment) != null;
if(!savedInstanceState != null) {
if(!mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(true);
} else if(mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(false);
}
return;
}
mManager = getSupportFragmentManager();
mManager.addOnBackStackChangedListener(this);
final FragmentTransaction ft = mManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if (!mMenuFragmentVisible && mContentFragmentVisible) {
/*
* Only the content fragment is visible, will enable sliding menu
*/
setupSlidingMenu(true);
onToggle();
ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);
} else if (mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(false);
/*
* Both menu and content fragments are visible
*/
ft.replace(R.id.menuFragment, getCustomApplication().getMenuFragment(), MenuFragment.TAG);
ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);
}
if (mQuickAccessFragmentVisible) {
/*
* The quick access fragment is visible
*/
ft.replace(R.id.quickAccessFragment, getCustomApplication().getQuickAccessFragment());
}
ft.commit();
}
private void setupSlidingMenu(boolean enable) {
/*
* if enable is true, enable sliding menu, if false
* disable it
*/
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// launch the fragment that was clicked from the menu
}
#Override
public void onBackPressed() {
// Will let the user press the back button when
// the sliding menu is open to display the content.
if (mSlidingMenu != null && mSlidingMenu.isMenuShowing()) {
onShowContent();
} else {
super.onBackPressed();
}
}
#Override
public void onBackStackChanged() {
/*
* Change selected position when the back stack changes
*/
if(mSlidingMenuListView != null) {
mSlidingMenuListView.setItemChecked(getCustomApplication().getSelectedPosition(), true);
}
}
#Override
public void onToggle() {
if (mSlidingMenu != null) {
mSlidingMenu.toggle();
}
}
#Override
public void onShowContent() {
if (mSlidingMenu != null) {
mSlidingMenu.showContent();
}
}
}
The following is a stripped version of the CustomApplication. My thoughts behind this implementation was to guarantee only one instance of each fragment throughout my application's life cycle.
public class CustomApplication extends Application {
private Fragment mSsportsFragment;
private Fragment mCarsFragment;
private Fragment mMusicFragment;
private Fragment mMoviesFragment;
public Fragment getSportsFragment() {
if(mSsportsFragment == null) {
mSsportsFragment = new SportsFragment();
}
return mSsportsFragment;
}
public Fragment getCarsFragment() {
if(mCarsFragment == null) {
mCarsFragment = new CarsFragment();
}
return mCarsFragment;
}
public Fragment getMusicFragment() {
if(mMusicFragment == null) {
mMusicFragment = new MusicFragment();
}
return mMusicFragment;
}
public Fragment getMoviesFragment() {
if(mMoviesFragment == null) {
mMoviesFragment = new MoviesFragment();
}
return mMoviesFragment;
}
}
I am very interested in tips on how to best implement multiple fragments and how to maintain their states. For your information, my applicaion consists of 15+ fragments so far.
I have done some research and it seems that FragmentManager.findFragmentByTag() is a good bet, but I haven't been able to successfully implement it.
My implementation seems to work good except for the fact that mActivity references become null after orientation changes, which lets me to believe that I may have some memory leak issues as well.
If you need to see more code, please let me know. I purposely avoided including fragment code as I strongly believe issues are related to my Activity and Application implementations, but I may be wrong.
Thanks for your time.
My thoughts behind this implementation was to guarantee only one instance of each fragment throughout my application's life cycle
This is probably part, if not all, of the source of your difficulty.
On a configuration change, Android will re-create your fragments by using the public zero-argument constructor to create a new instance. Hence, your global-scope fragments will not "guarantee only one instance of each fragment".
Please delete this custom Application class. Please allow the fragments to be re-created naturally, or if they need to live for the life of a single activity, use setRetainInstance(true). Do not attempt to reuse fragments across activities.
I don't see where are you using the reference to mActivity. But don't hold a reference to it. Always use getActivity since the Activity can be recreated after orientation change. Also, don't ever set the fragment's fields by setters or by assigning always use a Bundle and Arguments
Best practice for instantiating a new Android Fragment
Also you can use setRetainInstance(true) to keep all the fragment's members during orientation change.
Understanding Fragment's setRetainInstance(boolean)
To resolve this problem you have to use the activity object provided by onAttach method of fragment so when you change the orientation fragment is recreated so onAttach give you the current reference
you can use onAttach(Context context) to create a private context variable in fragment like this
#Override
public void onAttach(Context context) {
this.context = context;
super.onAttach(context);
}
on changing orientation, onAttach gives you new reference to the context, if you want reference to activity, you can typecast context to activity.
Context can also be reassigned inside onCreate in fragments as OnCreate is called when device is rotated
private Context mContext;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//get new activity reference here
mContext = getActivity();
}
pass this mContext throughout the fragment
If you don't setRetainInstance(true) in onCreate ... the collection e.g List<Object>, Vector<Object> in Application class will get null. Make sure you setRetainInstance(true) to make them alive.

how to access button of fragment A from fragment B

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.

Categories

Resources