onBackPressed to fragment(A, B, C) - android

I have main activity and three fragment (one, two, three).
And all this fragments extands BaseFragment:
public abstract class BaseFragment extends Fragment {
private Toolbar mToolbar;
private ActionBar mActionBar;
#Override
#CallSuper
public void onAttach(Activity context) {
super.onAttach(context);
mToolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
mActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
//mActionBar.setTitle(setMyTitle());
//Log.i("BaseFragment", "onAttach = "+getBackStackCount());
resetToolbarNavigation(getBackStackCount()!=0);
}
//protected abstract String setMyTitle();
#Override
#CallSuper
public void onDetach() {
super.onDetach();
Log.i("BaseFragment", "onDetach = " + (getBackStackCount() - 1));
resetToolbarNavigation((getBackStackCount() - 1 )!= 0);
}
private int getBackStackCount() {
int b = getActivity().getSupportFragmentManager().getBackStackEntryCount();
Log.i("BaseFragment", "getBackStackCount = "+b);
return b;
}
private void resetToolbarNavigation(boolean backNavigationEnabled) {
mActionBar.setDisplayHomeAsUpEnabled(backNavigationEnabled);
Log.i("BaseFragment", "resetToolbarNavigation");
if (backNavigationEnabled) {
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.i("resetToolbarNavigation", "setNavigationOnClickListener");
getActivity().onBackPressed();
}
});
}
else {
((StartPageActivity) getActivity()).initToolbar();
((StartPageActivity) getActivity()).syncState();
}
}
}
But when i click back arrow i got Exeption
09-22 19:28:13.233 5643-5643/com.test.mylist E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NullPointerException
at com.test.exemple.BaseFragment$1.onClick(BaseFragment.java:56)
at android.view.View.performClick(View.java:2485)
at android.view.View$PerformClick.run(View.java:9080)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3683)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
onBackPressed inside main activity
#Override
public void onBackPressed() {
getSupportFragmentManager().popBackStack();
Log.d("StartPageActivity", "onBackPressed " + getSupportFragmentManager().getBackStackEntryCount());
}
Can you help me? Where is the error? What am I doing wrong?

Not sure why you're getting that exact exception, but your approach to use the fragment is not so good in the way it's designed and that could bring this type of problems. So here I leave you some advices that has been useful to me when implementing apps with fragments:
Separate responsibilities: Activity should take care of it's stuff and Fragment should only be responsible for Fragment's functionalities. To achieve this you could make your Fragment publish interfaces with the methods you need the Activity implements and then just use it in the Fragment. For example in your code you have strong relations in your fragment form your Activity. Method resetToolbarNavigation is interacting with Views that belong to the Activity, notice that method is not doing nothing that is Fragment responsibility, so that entire method should be implemented in the activity, then you can publish a interface in your BaseFragment for example:
//Get the instance of the activity implementation
private BaseFragmentActions baseFragmentActions;
//Publish your interface with the methods your Activity will implement
public interface BaseFragmentActions {
public void resetToolbarNavigation(boolean backNavigationEnabled);
}
//In your onAttach get the implementation from your activity
#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 {
baseFragmentActions = (BaseFragmentActions)activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement interface BaseFragmentActions");
}
}
Then in your Activity just implement the method and use it in your fragment like baseFragmentActions.resetToolbarNavigation(true); Notice that this way BaseFragment doesn't care about the activity, just ask that the activity that contain it have to implement the methods it needs. To achieve this you can use EventBus too.
The transactions between fragments should be done always in the Activity that contain it. A Fragment should not know/cares about which Fragment should be loaded after.
Use the concept "Activity knows it's Fragments but Fragment don't know it's Activity"
You can do it this way and you can have information of your Activity in your Fragment but that could bring bigs headaches like this one.
Hope it helps you as concept for future design and sorry for my English

Here's what I think is happening based on your comments.
you open to fragment 1 and resetToolbarNavigation(boolean) gets called from onAttach
you navigate to fragment 2 and resetToolbarNavigation(boolean) gets called from onAttach
you navigate to fragment 3 and resetToolbarNavigation(boolean) gets called from onAttach
you click the navigation button and fragment 3 handles it properly popping the backstack and navigating you back to fragment 2
resetToolbarNavigation(boolean) is not called from onAttach on fragment 2 because the fragment/activity wasn't destroyed and the fragment was never detached.
you press the navigation button and it is handled by fragment 3 again which isn't started so getActivity() returns null
You should try setting the on navigation listener when the fragment displayed. If you are performing your fragment transactions correctly, you should be able to do this in onResume().

Related

Android Nested Fragment and Interface

I have a Fragment A which has an interface that will return the value of a TextView. The Fragment A is initialized and Attached to FragmentB. Here is the code for Fragment A and B.
Fragment A
public class FragmentA extends Fragment {
...
public interface Listener {
void onValue(int value);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Listener) {
listener = (Listener) context;
} else {
throw new RuntimeException(context.toString() + " must implement Listener");
}
}
}
Fragment B
public class FragmentB extends Fragment implements FragmentA.Listener {
...
private void initFragmentA() {
FragmentManager fragmentManager = getChildFragmentManager();
fragmentA = FragmentA.newInstance();
fragmentManager.beginTransaction().add(container, fragmentA, TAG).commit();
}
#Override
public void onValue(int value) {
}
}
When I start the app, a error occurred:
Java.lang.RuntimeException: ####.page.MainActivity#1f7f316c must implement Listener
at ####.widget.FragmentA.onAttach(FragmentA.java:66)
The MainActivity contains the Fragment B, but the Fragment B has already implemented the interface of Fragment A. Why the error occurred? The interface implemented in parent Fragment doesn't work?
From Android Developer website, it says:
Often you will want one Fragment to communicate with another, for example to change the content based on a user event. All Fragment-to-Fragment communication is done through the associated Activity. Two Fragments should never communicate directly.
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.
Therefore, when you implement that interface in Fragment B, it throws a RuntimeException error.
Summary: you have to implement that interface in your fragment hosting activity.
You can learn more on how to pass data between fragments thru a hosting activity (usually we make use of Bundle) at http://www.vogella.com/tutorials/AndroidFragments/article.html
The error message is pretty clear. When you use onAttach, the context passed through is actually the host Activity - the biggest boss of all, not the parent fragment. So FragmentB which implement the interface but will not receive any update.
You should use getParentFragment to access FragmentB from FragmentA, then cast it to the Listener interface.

Transitioning between Android Fragments using a Fragment controller

I am new to Android and I am trying to build an app containing three fragments: let's say A, B, and C. I want a button on A to show me B when clicked, and a button on B to show me C when clicked. I understand that one way is to use FragmentManager like this: in fragment A, I can have a button click listener that does
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, new B())
.commit();
However, I do not want A to know about fragment B, or B to know about C. I was thinking of creating some kind of FragmentController to solve this problem such that the Controller would know about the necessary transitions and maintain a state machine.
Is this a common pattern in Android? I tried googling but I didn't see many code examples for this pattern. How can I decouple the different fragments so that the fragments don't have to worry about the transitions?
Thanks.
Yes, it is a common pattern, you need understand how to work the comunnication between fragments.
Taking on, your fragments are contained in an activity, maybe you can do the next:
The Fragment listener
public interface FragmentNavigationListener {
public void onNavigateTo(int fragment);
}
The Activity which implements callback
public static final int Fragment FRAGMENT_A = 0;
public static final int Fragment FRAGMENT_B = 1;
#Override
public void onNavigateTo(int fragment){
switch fragment{
case FRAGMENT_A :
...
case FRAGMENT_B :
...
}
}
Each Fragment
must encore that parent activity implements the listener
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (FragmentNavigationListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement FragmentNavigationListener");
}
}
In OnclickListener from each button in each fragment you call the listener.
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
mCallback.onNavigateTo(FRAGMENT_B);
}
});
Write fragmentInteraction interfaces, define methods in this interface, then onAttach of the fragment lifeCycle do this
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
That way every activity to which this fragment gets attached to must implement the OnFragmentInteractionListener. Now in your onClick method do mListener.onClick(view) assuming you have a method called onClick(View v) defined in your interface. And in your activity's implementation of the onClick function do the fragmentTransaction

When is fragment finally attached to activity?

I have a main fragment with a viewpager inside it. This viewpager has 2 pages (list fragments). When I start the activty, the main fragment is shown and I also show the first paged fragment. This paged fragment displays data from a db using AsyncTask.
In the main fragment I have:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
onPageSelected(0);
}
#Override
public void onPageSelected(int position) {
Fragment fragment = (Fragment) pagerAdapter.instantiateItem(viewPager, position);
if (fragment instanceof IPagedFragment) {
((IPagedFragment) fragment).onShown(getActivity());
}
}
And the interface is:
public interface IPagedFragment {
void onShown(FragmentActivity activity);
}
The first issue I have is that I have to pass the activity as a parameter because when onShown gets called, the activity is still null.
Furthermore, the paged fragments use progressbar logic similar to the LoginActivity sample. I also get the following exception:
IllegalStateException: Fragment PagedFragment1{4201f758} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:620)
So what is the correct stage to start retrieving data from db once the paged fragment is fully available to the UI?
Issues like yours is the reason some developers are starting to question if fragments are really that good or useful.
Also "the correct" is debatable as you can do it in a variety of places and different developers will give you different answers, But let me try to supply you some useful info.
The attach/detach callbacks:
public void onAttach(Activity activity);
public void onDetach();
between those two methods any call to getActivity() will return the non-null activity the fragments is connected to. You can override them and use a private boolean isAttached to keep track of that call.
Also useful is the:
public void onActivityCreated (Bundle savedInstanceState)
this method is called AFTER the Activity.onCreate method. That is very important if you rely on some initialisation that happened there.
Also it's important to remember that on the moment the fragment transaction happens, the Fragment.onCreate happens after the Activity.onCreate and during rotation it happens before it.
As a general rule of thumb I use the Fragment.onStart() / Fragment.onStop() for getting/listening to data. On those calls, all the UI have been created, the fragment is attached to the activity and those callbacks don't get called if there's a dialog/popup (pause/resume does)
From the documentation:
public void onActivityCreated (Bundle savedInstanceState)
[...] tells the fragment when it is fully associated with the new activity instance.
source: http://developer.android.com/reference/android/app/Fragment.html#onActivityCreated(android.os.Bundle)
To get the reference of your activity, create a local object of fragmentActivity and get your activity reference as shown below.
private FragmentActivity fragmentActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
fragmentActivity=activity;
}

Suggested way for common fragment listener

For Fragment-Activity communication, this is the suggested way of doing it, by using a listener.
In my case I have two fragments and a button at each and I would like them to do the exact same thing when pressed.
Should I create a separate listener class that the Activity implements and then instantiate a listener in each fragment or there is a better design that I am not aware of?
EDIT
I am sorry, I probably didn't communicate that properly. I am not looking for communication between fragments. I have a Fragment A with a buttonA and a Fragment B with a buttonB. When I click on buttonA, there is a listener in my Activity and method doSomething() is called. Now I want buttonB calling doSomething() too. Should I A) create a second listener and have the activity implement that too, B) create one separate listener class and use this one for both or C) a better choice ??
For communication between fragment to frament or activity to fragment communication via events. There are few alternatives are there e.g. this otto eventbus I know. and the tutorial about this can be found Here or just google it.
As from the documentation :
Two Fragments should never communicate directly.
So you best follow the pattern explained in the article and communicate thru the activity on which the fragments are attached.
When a listener is called from fragment A then get the fragment B from the fragmentManger
YourFragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_b);
fragment.doSomething();
I'm using this pattern and it works well for me:
public class Fragment1 extends Fragment {
FragmentListener mCallback;
public interface FragmentListener {
public void onAction1();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof FragmentListener) {
mCallback = (FragmentListener) activity;
}
}
public void onAction2() {
// do your stuff...
}
}
public class Fragment2 extends Fragment {
FragmentListener mCallback;
public interface FragmentListener {
public void onAction2();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof FragmentListener) {
mCallback = (FragmentListener) activity;
}
}
public void onAction1() {
// do your stuff...
}
}
public class MainActivity implements Fragment1.FragmentListener, Fragment2.FragmentListener {
private Fragment1 fragment1;
private Fragment2 fragment2;
/**
* Listening to events from first fragment and forwarding to second fragment
*/
#Override
public void onAction1() {
fragment2.onAction1();
}
/**
* Listening to events from second fragment and forwarding to first fragment
*/
#Override
public void onAction2() {
fragment1.onAction2();
}
}
The Activity listens to "events" from the fragments and if needed forward it to the other fragment(s).

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