How to get all sub/inner/nested Fragments of a Fragment? - android

It looks like it's possible to get all fragments of an Activity pretty easily. But how can I get all subfragments for a given fragment ?
This question is also related to getParentFragment API 16

You can do it in the same way -- just use the FragmentManager obtained using the Fragment instance's getChildFragmentManager() instead of the Activity FragmentManager. Of course, this assumes you're using a recompiled version of the support library with getFragments() not hidden, or are using reflection to get invoke that method.

The following solution is not perfect but it works in some extent :
If Android SDK is 17+, then it works fine
below SDK 17 it works for fragments at the root level (added directly to activity), and also works fine for fragments of level 1 (added to a fragment at root level).
for fragments of level >= 2, then it will always return a fragment of root level. It means that it is not possible to return the real parent of a fragment whose level is >=2, it will always return its ancestor at the root level.
And, unfortunately, it means you must have access to the activity class, so this solution is not really generic.
Here is the solution. The MyActivity class is given below.
public static Fragment getParentFragment(Fragment fragment) {
if( Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN)
return fragment.getParentFragment();
MyActivity activity = (MyActivity)fragment.getActivity();
List<Fragment> fragmentList = activity.getActiveFragments();
if( fragmentList.contains( fragment) ) {
return null;
}
for( Fragment fragmentLevel1 : fragmentList ) {
if( fragmentLevel1.getFragmentManager() == fragment.getFragmentManager() ) {
return fragmentLevel1;
}
}
//this is not supposed to happen, it might be better to throw an exception
return null;
}
Where MyActivity is based on : Is there a way to get references for all currently active fragments in an Activity?
public class MyActivity {
List<WeakReference<Fragment>> fragList = new ArrayList<WeakReference<Fragment>>();
#Override
public void onAttachFragment (Fragment fragment) {
fragList.add(new WeakReference(fragment));
}
public List<Fragment> getActiveFragments() {
ArrayList<Fragment> ret = new ArrayList<Fragment>();
for(WeakReference<Fragment> ref : fragList) {
Fragment f = ref.get();
if(f != null) {
if(f.isVisible()) {
ret.add(f);
}
}
}
return ret;
}
}

Related

Fragment communication: which is the better approach?

I was working on communication between multiple fragments in a activity stack.
I have figured out 2 ways to do this.
Through interfaces
Through Bundle setarguments
Bundle bundle = new Bundle();
bundle.putBoolean("Status",trur);
Fragment fragment = getFragmentManager().findFragmentByTag(bottomfragment.class.getName());
if(fragment!=null) {
fragment.setArguments(bundle);
}
I felt the 2nd approach easy.Since Google recommends 1 st approach
Can anyone help me with the problems I may face by following 2nd approach.
You are mixing the both the ways.
1. through interfaces is if you want to communicate from fragment to activity or fragment to fragment(via activity)
2. set argument is if you want to pass arguments while starting the fragment. you can call methods of fragment using the instance you get from fragment id/tag
Please referfragment communication
Try to communication between two fragments like this:
1) Create Interface like this:
public interface FragmentChangeListener {
void changeFragment(Fragment fragment);
}
2) Update MainActivity like this:
public class MainActivity extends AppCompatActivity implements FragmentChangeListener
{
//Activity code
------
#Override
public void changeFragment(Fragment fragment) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction tr = fm.beginTransaction();
tr.replace(R.id.frame_container, fragment);
tr.commitAllowingStateLoss();
}
}
3) Create First Fragment:
public class FirstFragment extends Fragment
{
// call another freagment like this
//in your oncreateview method:
SecondFragment
Bundle b = new Bundle();
b.putSerializable(SELECTED_ITEM, true);
SecondFragment second = SecondFragment.newInstance(b);
FragmentChangeListener fc = (FragmentChangeListener) getActivity();
fc.changeFragment(second);
}
4) Second Fragment:
public class SecondFragment extends Fragment
{
public static SecondFragment newInstance(Bundle bundle) {
SecondFragment fragment = new SecondFragment();
if (bundle != null)
fragment.setArguments(bundle);
return fragment;
}
//another fragment related code
//In your OncreateView like this:
if (getArguments() != null)
boolean temp = getArguments().getBoolean(IntentParameter.SELECTED_ITEM);
}
Hope this explanation help you :)
Argument (Bundle) should be passed to Fragment only initially (when Fragment's object is created by default constructor). Calling setArguments method on already added Fragment will cause IllegalStateException. See body of setArguments method:
public void setArguments(Bundle args) {
if (mIndex >= 0 && isStateSaved()) {
throw new IllegalStateException("Fragment already active and state has been saved");
}
mArguments = args;
}
If you want to change something in Fragment A from Fragment B :
a) Get an object of A inside B using
getFragmentManager().findFragmentByTag("FRAGMENT_A_TAG");
Or
getFragmentManager().findFragmentById(FRAGMENT_A_CONTAINER_ID);
Cast returned object to A and call proper method on it. (It's the simplest way, but after it, A and B become highly coupled);
b) Alternatively, you can write mentioned logic inside method of Activity, which contains these 2 Fragments, get reference of this Activity inside B using getContext() casted to container Activity and call mentioned method on it (It kills reusability, because if you want to have A and B on other Activity, casting getContext() will cause ClassCastException);
c) The best way, to communicate between Fragments is to create interface, implement container Activity by this interface, get reference of this interface inside B and call proper method on it. (You can implement as many activities as you want by this interface, so it's reusable approach and A and B are loosely coupled).

What if onAttach() is not overridden inside the fragment code?

According to fragment lifecycle onAttach() is called before onCreate() so that it assigns hosting activity to the fragment. So, I wanted to know what if it is not overridden. Does a default definition for all the fragment callbacks already exists?
From the documentation:
void onAttach (Activity activity)
called once the fragment is associated with its activity. This method was deprecated in API level
23. Use onAttach(Context) instead.
If you override this method you must call through to the superclass
implementation.
void onAttach (Context context)
Called when a fragment is first attached to its context. onCreate(Bundle) will be called after this.
This is a lifecyle design for the fragment. There is nothing wrong when you don't override the method.
Does a default definition for all the fragment callbacks already exists?
No, you need to create the fragment callback by yourself. onAttach() method is usually overriden to make sure the parent activity of the fragment is implementing the fragment callback. Something like this (read more at Communicating with Other Fragments):
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(Context context) {
super.onAttach(context);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnHeadlineSelectedListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
...
}
When the parent activity is not implementing OnHeadlineSelectedListener, the application will crash and throwing must implement OnHeadlineSelectedListener. Hence it will preventing you introducing a logic error in your code.
UPDATE
What the purpose of onAttach()?
According to fragment lifecycle onAttach() is called before onCreate()
so that it assigns hosting activity to the fragment.
What the meaning of that actually?
Simple answer: It's a lifecyle of Fragment where we can know when the Fragment has been attached to it's parent activity.
More details:
From the the following source code of onAttach():
/**
* Called when a fragment is first attached to its context.
* {#link #onCreate(Bundle)} will be called after this.
*/
#CallSuper
public void onAttach(Context context) {
mCalled = true;
final Activity hostActivity = mHost == null ? null : mHost.getActivity();
if (hostActivity != null) {
mCalled = false;
onAttach(hostActivity);
}
}
/**
* #deprecated Use {#link #onAttach(Context)} instead.
*/
#Deprecated
#CallSuper
public void onAttach(Activity activity) {
mCalled = true;
}
We will see nothing except the documentation about our previous question and
mHost.
on the source code of Fragment at https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/Fragment.java#L435, we can know that the mhost is actually a FragmentHostCallback:
// Activity this fragment is attached to.
FragmentHostCallback mHost;
But if we scanning through all the source code Fragment, we won't get any clue where the mhost is initialized.
We know that from the Fragment lifecyle diagram that the lifecyle is start when the fragment is added:
Programatically, we add the Fragment with:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
Checking the FragmentManager source code at line 1200 to 1229 from method moveToState():
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
}
we have the following code:
f.mHost = mHost;
f.mParentFragment = mParent;
f.mFragmentManager = mParent != null
? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
// If we have a target fragment, push it along to at least CREATED
// so that this one can rely on it as an initialized dependency.
if (f.mTarget != null) {
if (mActive.get(f.mTarget.mIndex) != f.mTarget) {
throw new IllegalStateException("Fragment " + f
+ " declared target fragment " + f.mTarget
+ " that does not belong to this FragmentManager!");
}
if (f.mTarget.mState < Fragment.CREATED) {
moveToState(f.mTarget, Fragment.CREATED, 0, 0, true);
}
}
dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
f.mCalled = false;
f.onAttach(mHost.getContext());
if (!f.mCalled) {
throw new SuperNotCalledException("Fragment " + f
+ " did not call through to super.onAttach()");
}
if (f.mParentFragment == null) {
mHost.onAttachFragment(f);
} else {
f.mParentFragment.onAttachFragment(f);
}
Now we know that mHost and onAttach() of Fragment is initialized and called by the FragmentManager.
Nothing happens if you don't call OnAttach(). It is a lifecycle method provided to you if want to do something when the fragment is attached to its activity or context.
However, the fragment class does have a default implementation of OnAttach (which doesn't do anything). If you are curious, check out the source code.

Pattern for Activity / Fragment in android

I've a activity which basically is :
public class FragmentContainer extends FragmentActivityBase implements IRefreshListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getExtras() == null
|| getIntent().getExtras().get("type") == null) {
showProductList();
}
else
{
if (getIntent().getExtras().get("type").equals("customer"))
showCustomerList();
}
#Override
public void showProductList() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
// load the product list
ProductList fragment = new ProductList();
fragmentTransaction.replace(R.id.fragment_container, fragment)
.addToBackStack(null);
fragmentTransaction.commit();
}
.....
}
in the fragment, I use onCreateView to get intent and then I create my view.
If I need to change the fragment, I get the reference to the parent Activity (taken from onAttach) and I call method referenced by the IRefreshListener.
like :
IRefreshListener mCallback;
#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 = (IRefreshListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement IRefreshListener");
}
}
public void callCustomer() {
mCallback.showCustomerList();
}
It works but whne I change the orientation, even I use setRetainInstance(true) it will be reseted.
I have 2 questions :
Do I use the good pattern to manage my application. The big activity which contains one fragment become bigger with the time
How should I handle orientation change ?
Regards
I do not find this pattern is more perfect or best one, although it is or was a suggestion from Google. Because it could be a worse coding style if fragment knows particular activity or listeners, you might write more and more code, when you wanna to let your fragment know more its "container" or "parents". Will the fragment later be used for other activity which has not been implemented with IRefreshListener etc, you will code much more.
My introduce is using Otto-Bus or Event-Bus. You can just send message from one to one. Every one doesn't have to know each other.

Determine if Parent is an Activity or FragmentActivity

I have a custom widget saved as an android library project.
The custom widget uses Fragments and so needs to access the Application's FragmentManager.
I would like my custom widget to be compatible with Applications that extend Activity (Honeycomb or higher) and also Applications that extend FragmentActivity.
To achieve this I need my custom widget to decide whether to use getFragmentManager() or getSupportFragmentManager() based on whether the parent extends Activity or FragmentActivity as shown below.
switch (getApplicationType()) {
case ACTIVITY__HONEYCOMB_ONWARD
FragmentManager fm = ((Activity)getContext()).getFragmentManager();
//...
break;
case FRAGMENT_ACTIVITY
FragmentManager fm = ((FragmentActivity)getContext()).getSupportFragmentManager();
//...
break;
//...
The bit that has me stumped is how to write the tests in my getApplicationType() method.
private int getApplicationType() {
if (??? How do I write this test ???) {
return ACTIVITY__HONEYCOMB_ONWARD;
} else if (??? How do I write this test ???) {
return FRAGMENT_ACTIVITY;
} else {
//...
}
}
Use instanceof.
For example:
if ( getParent() instanceof Activity ) {
return ACTIVITY;
} else if ( getParent() instanceof Fragment ) {
return FRAGMENT;
} etc…

Fragment getActivity() returns null in Activity JUnit test

I wrote Android JUnit test for Activity that instantiates fragments (actually tabs). During the test, when I try to do anything with these tabs, they crash because getActivity() method in them returns null. The actual application (not a test) never shows this behavior and fragment getActivity() always returns the right parent activity there. My test case looks like:
public class SetupPanelTest extends ActivityUnitTestCase<MyAct> {
FSetup s;
public SetupPanelTest() {
super(MyAct.class);
}
protected void setUp() throws Exception {
super.setUp();
startActivity(new Intent(), null, null);
final MyAct act = getActivity();
AllTabs tabs = act.getTabs();
String tabname = act.getResources().getString(R.string.configuration);
// This method instantiates the activity as said below
s = (FSetup) tabs.showTab(tabname);
FragmentManager m = act.getFragmentManager();
// m.beginTransaction().attach(s).commit();
// ... and even this does not help when commented out
assertTrue(s instanceof FSetup); // Ok
assertEquals(act, s.getActivity()); // Failure
}
public void testOnPause() {
// this crashes because s.getActivity == null;
s.onPause();
}
}
The AllTabs creates a fragment, then required, in this way:
FragmentManager manager = getFragmentManager();
Fragment fragment = manager.findFragmentByTag(tabname);
if (fragment == null || fragment.getActivity() == null) {
Log.v(TAG, "Instantiating ");
fragment = new MyFragment();
manager.beginTransaction().replace(R.id.setup_tab, fragment, tabname).commit();
....
Here, all fragments are initially placeholders that are later replaced by the actual fragments:
<FrameLayout
android:id="#+id/setup_tab"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
The logcat shows that the new fragment has been instantiated. In the same layout, there is also the previously mentioned AllTabs fragment that seems not having this problem (where and how it gets FragmentManager otherwise):
<TabWidget
android:id="#android:id/alltabs"
...
Most impressively, when I call attach directly on the fragment manager obtained on the right activity, this still has no effect. I tried to put five seconds delay (I have read that transaction may be delayed), I tried to call the rest of the test through runOnUiThread - nothing helps.
The question is that is need to do so to attach my fragments to the activity also during the test. I have fragment and I have activity, I cannot attach one to another.
Even if you call .commit() on transaction, it is still not done, fragments are attached only lazily.
FragmentManager m = activity.getFragmentManager();
m.executePendingTransactions();
This finally attaches all fragments to the activity. Seems redundant when running the application itself but required in JUnit test case.

Categories

Resources