I have a Activity A, a ListFragment L and 2 Fragments F1 and F2.
When the app is launched, A is created, which loads L. Based on what user clicks, it is replaced by F1 or F2
I have implemented a Navigation Drawer which shows certain items to the user. However, since I have implemented the Navigation Drawer in the Activity, it shows for all the Fragments. but i want show A is visible
(Very much similar to Googles Gmail app. When the user is on the main screen - the drawer is present. When user taps to open an email, the drawer changes to back button)
I am not sure how to translate the above code. thanks in advance
In your inner fragment(like view mail)
#Override
public void onPause()
{
super.onPause();
((MyActivity) getActivity()).enableToggle();
}
#Override
public void onResume()
{
super.onResume();
((MyActivity) getActivity()).disableToggle();
}
I hope there will be 'NavigationDrawerFragment'in your activity, add this methods:
public void disableToggle()
{
if (mNavigationDrawerFragment != null)
mNavigationDrawerFragment.disableToggle();
}
public void enableToggle()
{
if (mNavigationDrawerFragment != null)
mNavigationDrawerFragment.enableToggle();
}
In NavigationDrawerFragment add this methods:
Also check that you are using android.support.v7.app.ActionBarDrawerToggle;
public void disableToggle()
{
if (mDrawerToggle != null)
mDrawerToggle.setDrawerIndicatorEnabled(false);
}
public void enableToggle()
{
if (mDrawerToggle != null)
mDrawerToggle.setDrawerIndicatorEnabled(true);
}
Edit: see snapshots for above code
MainFragment
DetailFragment
Related
In my application I use ViewPager for show two fragments into activity.
In one of this fragments I use NavigationDrawer, I want when click on onBackPress close this NavigationDrawer.
I write below code for open this Drawer :
reviewSerialFrag_DrawerLayout.openDrawer(Gravity.END);
I want when click on onBackPress close this drawer with below code :
reviewSerialFrag_DrawerLayout.closeDrawer(Gravity.END);
How can I it? Please help me
/Try to use below code snipet/
#Override
public void onBackPressed()
{
int count = getFragmentManager().getBackStackEntryCount();
// count --> Is your current fragment
if (count == 0)
{
if(reviewSerialFrag_DrawerLayout.isDrawerVisible(GravityCompat.END))
{
reviewSerialFrag_DrawerLayout.closeDrawer(GravityCompat.END);
}
}
}
In your Activity onBackPressed() write below code
#Overrdie
public void onBackPressed(){
Fragment currentFragment = getActivity().getFragmentManager().findFragmentById(R.id.fragment_container);
if(currentFragment instanceof YourDrawerFragment && reviewSerialFrag_DrawerLayout.isDrawerVisible(GravityCompat.END))
{
reviewSerialFrag_DrawerLayout.closeDrawer(GravityCompat.END);
return;
}
super.onBackPresses():
}
You can do this to close your drawer on back press
#Overrdie
public void onBackPressed(){
super.onBackPresses():
if(reviewSerialFrag_DrawerLayout.isDrawerVisible(GravityCompat.END)){
reviewSerialFrag_DrawerLayout.closeDrawer(GravityCompat.END);
}
}
In general, I find it easiest to use the Observer pattern and delegate the back pressed event down to the fragment. This will allow you to keep the activity and fragment concerns separated.
interface OnBackPressedListener {
public void onBackPressed();
}
Then, in your fragment, implement this OnBackPressedListener
class MyFragment extends Fragment implements OnBackPressedListener{
public void onBackPressed(){...}
}
And finally, in your activity, you can do the following:
class MyActivity extends Activity {
#Override
public void onBackPressed(){
// Grab all the fragments that are 'observing' the back press event
Fragment currentFragment =
getFragmentManager().findFragmentById(R.id.fragment_container);
if(currentFragment != null && currentFragment instanceof OnBackPressedListener) {
// delegate this back press event down to the fragment
OnBackPressedListener backFragment = (OnBackPressedListener) currentFragment;
backFragment.onBackPressed();
}
super.onBackPressed():
}
In Jon's solution, the super.onBackPressed will always be called. This may not necessarily be the case.
To do this, it is enough that the implementation returns a boolean and act according to the result.
My solution almost identical here
public interface IOnBackPressed {
/**
* Si vous retouné true le back press ne sera pas pris en compte, sinon l'activité agira naturellement
* #return true si votre traitement est prioritaire sinon false
*/
boolean onBackPressed();
}
see link for more details
In my activities I frequently use this idiom:
#Override
public void onDestroy() {
super.onDestroy();
if (isFinishing() != true) return;
// do some final cleanup since we're going away for good
}
Fragment has an onDestroy() method, but what is the equivalent of isFinishing()? Should I just check getActivity().isFinishing() from within the fragment's onDestroy()?
EDITED TO ADD:
Here are the callbacks (in order) I get under various circumstances, along with whether getActivity() returns null or non-null and, if non-null, the value of getActivity().isFinishing():
Scenario #1: DialogFragment is showing and user taps back button (i.e. need to release references):
onDismiss(): activity = non-null, finishing = false
onDestroy(): activity = non-null, finishing = false
Scenario #2: DialogFragment is showing and parent activity finishes (i.e. need to release references):
onDestroy(): activity = non-null, finishing = true
onDismiss(): activity = null, finishing = n/a
Scenario #3: DialogFragment is showing and OS temporarily destroys parent activity (i.e. should not release references):
onDestroy(): activity = non-null, finishing = false
onDismiss(): activity = null, finishing = n/a
Fragments have a method called isRemoving() which is true when the fragment is being removed from the Activity:
Return true if this fragment is currently being removed from its activity. This is not whether its activity is finishing, but rather whether it is in the process of being removed from its activity.
Thanks for the help guys. I ended up adding these to my DialogFragment:
private void doReleaseReferences() {
// release the stuff
}
#Override
public void onDestroy() {
super.onDestroy();
if (getActivity().isFinishing())
doReleaseReferences();
}
#Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (getActivity() != null)
doReleaseReferences();
}
Based on the callback behavior I added to the original question I think this serves my purposes. When the back button is pressed or the parent activity finishes the references are released; when the parent activity is destroyed by the OS with the intention of being revived later they are not released.
If you would like to clean up fragment references on per basis you could use this helper method for fragment replacement
public abstract class BaseActivity extends AppCompatActivity {
#Override
protected void onDestroy() {
if (isFinishing()) {
notifyStackedFragmentsFinishing();
}
super.onDestroy();
}
protected void changeFragment(Fragment fragment) {
notifyStackedFragmentsFinishing();
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment, fragment)
.commit();
}
private void notifyStackedFragmentsFinishing() {
List<Fragment> fragments = getSupportFragmentManager().getFragments();
if (fragments != null && !fragments.isEmpty()) {
for (Fragment fragment : fragments) {
((BaseFragment) fragment).releaseOnFinishing();
}
}
}
}
Now you can extend all your activities from BaseActivity and all your fragments from BaseFragment. Also if you stacking fragments you should probably extend more i.e. onBackPressed()
I think this kotlin extension function will do the trick. At least if you are using the "Navigation framework"
fun Fragment.isFinishing(): Boolean {
val name = this.javaClass.name
val thisEntry = findNavController().backQueue.find {
val destination = it.destination
destination is FragmentNavigator.Destination && destination.className == name
}
return thisEntry == null
}
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.
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 have an Android application that we are porting over to Honeycomb/Android 3.0 and we are using Fragments in our new interface.
I have search working via the widget as shown here.
The problem is, the widget doesn't pop up any more when using Fragments. So the question is how do I get search to be used with Fragments?
Or how can I replace this line to be used with Fragments?
getActivity().onSearchRequested();
I solved this problem using interface/callbacks.
In my MainActivity, I write a callback interface:
private SearchRequestedCallback mSearchRequestedCallback;
public void setSearchRequestedCallback(SearchRequestedCallback callback) {
mSearchRequestedCallback = callback;
}
public interface SearchRequestedCallback {
void onSearchRequested();
}
In my Fragment, I set the callback in the onStart() and unset it in the onStop():
#Override
public void onStart() {
super.onStart();
getActivity().setTitle(getResources().getString(R.string.app_name));
((MainActivity)getActivity()).setSearchRequestedCallback(new SearchRequestedCallback() {
#Override
public void onSearchRequested() {
addFilter();
}
});
}
#Override
public void onStop() {
((MainActivity)getActivity()).setSearchRequestedCallback(null);
super.onStop();
}
You can't. The SearchManager is implemented to work with Activitys, not Fragments. See this post for more information.