I'm trying to use support fragment manager in a fragment but it says SupportFragmentManager doesn't exist in the current context.
Here's my code
private void Adapter_RateItemClick(object sender, DriversAdapterClickEventArgs e)
{
RatingFragment editAluminiFragment = new RatingFragment();
var trans = SupportFragmentManager.BeginTransaction();
editAluminiFragment.Show(trans, "Rate");
}
I used
using FragmentManager = Android.Support.V4.App.FragmentManager;
?
what should I do next
Firstly, there is documentation AndroidX Fragment class (it is similar for support v4, but you definitely should switch to AndroidX if you can). And as you can see there, there are two methods for working with fragments: getChildFragmentManager and getParentFragmentManager. I suppose you want to use child fragment manager to show some dialog, so in your case it will be
private void Adapter_RateItemClick(Object sender, DriversAdapterClickEventArgs e)
{
RatingFragment editAluminiFragment = new RatingFragment();
editAluminiFragment.Show(getChildFragmentManager(), "Rate");
}
Related
So I have a Fragment(A) with a ListView. If I click the Listview's item, then I want to open another Fragment(B). My only problem is, that when I open the B Fragment, also the A is appear in the layout.
FragmentA nextFrag = new FragmentA(data);
getFragmentManager().beginTransaction()
.replace(R.id.fragment_a_layout, nextFrag)
.commit();
What is the proper way to open a new Fragment, without show the previuos one too?
#sweak So I understand, that I need an interface, which I already write in my FragmentA class:
public interface FragmentListener {
void replaceFragments();
}
Then I implement this interface in my MainActivity.class, if I am right.
#Override
public void replaceFragments() {
FragmentB nextFrag = new FragmentB();
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_layout, nextFrag)
.commit();
}
In my FragmantA onClickListener, I call this method: (FragmentListener listener)
listener.replaceFragments(recipeForFragment);
But now my problem is that my listener is null, so somehow I need to initializte, but I don't know how. Maybe I create a setter for it, in the FragmentA:
public void setFragmentListener(FragmentListener fragmentListener) {
this.listener = fragmentListener;
}
But where should I call this setter and when?
The existing answers are surprisingly incorrect.
If you have androidx.fragment:fragment-ktx:x.x.x (can't remember the latest version), you can do:
supportFragmentManager.commit {
replace(R.id.fragment_a_layout, nextFrag, "Some Tag If You need")
addToBackStack(null) // optional
}
Or even
supportFragmentManager.commit {
replace(R.id.fragment_a_layout, YourFragment::class.java)
}
In this way you don't even need those silly YourFragment.newInstance() methods, as the 3rd (optional) parameter is a Bundle for your args.
I'm sure there's a Java equivalent.
I am using a bottom navigation bar in my MainActivity to handle some fragments. This is the code used for switching between them:
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
if (item.isChecked &&
supportFragmentManager.findFragmentById(R.id.act_main_fragment_container) != null
)
return#OnNavigationItemSelectedListener false
val fragment =
when (item.itemId) {
R.id.navigation_home -> fragments[0]
R.id.navigation_bookings -> fragments[1]
R.id.navigation_messages -> fragments[2]
R.id.navigation_dashboard -> fragments[3]
R.id.navigation_profile -> fragments[4]
else -> fragments[0]
}
this replaceWithNoBackStack fragment
return#OnNavigationItemSelectedListener true
}
the method replaceWithNoBackstack is just a short-hand for this:
supportFragmentManager
?.beginTransaction()
?.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
?.replace(containerId, fragment)
?.commit()
The problem is that when i switch faster between them, my app crashes with the following exception:
java.lang.IllegalStateException: Restarter must be created only during owner's initialization stage
at androidx.savedstate.SavedStateRegistryController.performRestore(SavedStateRegistryController.java:59)
at androidx.fragment.app.Fragment.performCreate(Fragment.java:2580)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:837)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1237)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1302)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2075)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1865)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1820)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1726)
at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:150)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6709)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769)
I've been searching a lot and couldn't find an answer.
I also got this error if I do an API call, put the app in background, wait for the response, and at the time I go back to the app, the app crashes because I am trying to display a dialog fragment immediately (the reason I think this is happening is that the transaction of recreating the fragment when coming back from the background is still in progress at the time of displaying the dialog fragment). I solved this in a hacky way by setting a 500ms delay for the dialog because I couldn't figure out other solutions.
Please ask if you need more details regarding this.
Thank you in advance!
POSSIBLE TEMP SOLUTIONS
EDIT
I solved this issue by downgrading the app compat depedency to androidx.appcompat:appcompat:1.0.2 but this is just a temporary solution, since i will have to update it in future. I'm hoping someone will figure it out.
EDIT 2
I solved the issue by removing setTransition() from fragment transactions. At least I know the reason why android apps does not have good transitions in general
EDIT 3
Maybe the best solution to avoid this issue and also make things work smoothly is just to use ViewPager to handle bottom bar navigation
because the version 1.0.0 has not check the state, so it will not throw the exception,
but the version 1.1.0 changes the source code,so it throws the exception.
this is the Fragment version-1.1.0 source code, it will invoke the method performRestore
void performCreate(Bundle savedInstanceState) {
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
}
mState = CREATED;
mCalled = false;
mSavedStateRegistryController.performRestore(savedInstanceState);
onCreate(savedInstanceState);
mIsCreated = true;
if (!mCalled) {
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onCreate()");
}
mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
}
/**
the exception
**/
public void performRestore(#Nullable Bundle savedState) {
Lifecycle lifecycle = mOwner.getLifecycle();
if (lifecycle.getCurrentState() != Lifecycle.State.INITIALIZED) {
throw new IllegalStateException("Restarter must be created only during "
+ "owner's initialization stage");
}
lifecycle.addObserver(new Recreator(mOwner));
mRegistry.performRestore(lifecycle, savedState);
}
this is the version-1.0.0 source code,did not invoke the performRestore,so will not throw the exception
void performCreate(Bundle savedInstanceState) {
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
}
mState = CREATED;
mCalled = false;
onCreate(savedInstanceState);
mIsCreated = true;
if (!mCalled) {
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onCreate()");
}
mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
}
There are two different solution which can handle this:
The first solution is to split the transaction。
Because we always use replace or merge remove and add into one Transaction.
We can split the transaction to two transaction like this:
FragmentTransaction ft = manager.beginTransaction();
Fragment prev = manager.findFragmentByTag(tag);
if (prev != null) {
//commit immediately
ft.remove(prev).commitAllowingStateLoss();
}
FragmentTransaction addTransaction = manager.beginTransaction();
addTransaction.addToBackStack(null);
addTransaction.add(layoutId, fragment,
tag).commitAllowingStateLoss();
because this two transaction will be two different Message which will be handled by Handler.
The second solution is check the state in advance.
we can follow the source code,check the state in advance
FragmentTransaction ft = manager.beginTransaction();
Fragment prev = manager.findFragmentByTag(tag);
if (prev != null) {
if (prev.getLifecycle().getCurrentState() != Lifecycle.State.INITIALIZED) {
return;
}
ft.remove(prev);
}
I recommend the first way,because the second way is folowing the source code,if the source
code change the code, it will be invalid。
I had the same problem.
val fragment = Account.activityAfterLogin
val ft = activity?.getSupportFragmentManager()?.beginTransaction()
//error
ft?.setCustomAnimations(android.R.anim.slide_in_left,android.R.anim.slide_out_right)0
ft?.replace(R.id.framelayout_account,fragment)
ft?.commit()
Changing the library version did not help.
I solved this by adding the ft?.AddToBackStack(null) line after the ft?.setCustomAnimations () method and that’s it.
Animation works and there are no crashes.
If you're using 'androidx.core:core-ktx:1.0.2',
try changing to 1.0.1
If you're using lifecycle(or rxFragment) and androidx_appcompat:alpha05, try changeing versio.
ex) appcompat : 1.1.0-beta01 or 1.0.2
I think's that it appears as an error when saving the state when the target fragment is reused (onPause-onResume).
I changed implementation to api for androidx.appcompat:appcompat:1.0.2 and its worked for me
If it can help, I have encountered the same issue with a BottomNavigationView and setCustomAnimations, basically by switching quickly between Fragments, you may end up starting a FragmentTransaction while the previous one has not finished and then it crashes.
To avoid that, I disable the Navigation Bar until the transition is finished. So I have created a method to enable/disable the BottomNavigationView items (disabling the BottomNavigationView itself does not disable the menu or I didn't find the way) and then I re-enable them once the transition is completed.
To disable the items I call the following method right before starting a FragmentTransition:
public void toggleNavigationBarItems(boolean enabled) {
Menu navMenu = navigationView.getMenu();
for (int i = 0; i < navMenu.size(); ++i) {
navMenu.getItem(i).setEnabled(enabled);
}
}
To re-enable them, I have created an abstract Fragment class for the Fragments loaded from the BottomNavigationView. In this class, I overrides onCreateAnimator (if you use View Animation you should override onCreateAnimation) and I re-enable them onAnimationEnd.
#Nullable
#Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
if(enter){ // check the note below
Animator animator = AnimatorInflater.loadAnimator(getContext(), nextAnim);
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
myActivity.toggleNavigationBarItems(true)
}
});
return animator;
}
return super.onCreateAnimator(transit, enter, nextAnim);
}
Note: as my enter and exit animations have the same duration, I don't need to synchronise them as the enter animation starts after the exit one. That's why the if (enter) is sufficient.
I fixed this problem with add 'synchronized' into add fragment method
before :
public void addFragment(int contentFrameId, Fragment fragment, Bundle param, boolean addToStack) {
try {
if (!fragment.isAdded()) {
if (param != null) {
fragment.setArguments(param);
}
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
.add(contentFrameId, fragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
if (addToStack)
fragmentTransaction.addToBackStack(fragment.getClass().toString());
fragmentTransaction.commit();
}
} catch (IllegalStateException e) {
handleError(e.getMessage());
} catch (Exception e) {
handleError(e.getMessage());
}
}
after :
public synchronized void addFragment(int contentFrameId, Fragment fragment, Bundle param, boolean addToStack) {
try {
if (!fragment.isAdded()) {
if (param != null) {
fragment.setArguments(param);
}
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
.add(contentFrameId, fragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
if (addToStack)
fragmentTransaction.addToBackStack(fragment.getClass().toString());
fragmentTransaction.commit();
}
} catch (IllegalStateException e) {
handleError(e.getMessage());
} catch (Exception e) {
handleError(e.getMessage());
}
}
This bug seems to be resolved using androidx.appcompat:appcomat:1.1.0-rc01 and androidx.fragment:fragment:1.1.0-rc03
https://developer.android.com/jetpack/androidx/releases/fragment#1.1.0-rc03
I have this issue when using setCustomAnimations.
by removing setCustomAnimations solved my problem.
also I have no problem when I create new instance of fragment before showing it even using setCustomAnimation.
EDIT: another way is adding fragment to backstack.
I was able to fix this (hopefully 😃) by using commitNow() instead of commit() for all bottom nav fragment transactions.
I like this approach better as it allows you to still use custom transitions between fragments.
Note: This is a solution only if you don't want your bottom nav transactions to be added to backstack (which you should not be doing anyways).
Nothing worked except Drown Coder's solution, but it was still not perfect, because it adds transactions to backstack. So if you press all buttons in bottom navigation, you have at least 1 of every fragment in backstack. I slightly improved this solution, so you don't use .replace() that crashes app whith thansaction animations.
Here is the code:
if (getChildFragmentManager().getBackStackEntryCount() > 0) {
getChildFragmentManager().popBackStack();
}
FragmentTransaction addTransaction = getChildFragmentManager().beginTransaction();
addTransaction.setCustomAnimations(R.animator.fragment_fade_in, R.animator.fragment_fade_out);
addTransaction.addToBackStack(null);
addTransaction.add(R.id.frame, fragment, fragment.getClass().getName()).commitAllowingStateLoss();
I found another way of creating this case.
CASE-1
Inflate a fragment in frame-layout at an activity
start an API request (don't consume the api response when app in foreground)
Keep your app in background
Consume the API request (suppose you want to add another fragment on api response)
Inflate another fragment using .replace() method on the same frame-layout
You will be able to create the Crash
CASE-2
Inflate a fragment in frame-layout at an activity
Start an API request
Consume the api in foreground (suppose you want to add another fragment on api response, using .replace() method of fragment-manager)
Put your app in background
Recreate your application (you can do this using "Don't keep activities", changing permission, changing system language)
Come back to your application
Your activity will start re-creating
Activity will auto recreate its already inflated fragment suppose it is of (point-1)
Make sure API is request again in on recreate case, after point-8
Consume API response and inflate another fragment using .replace() method
You will be able to create the Crash (As in this case, already a transition is running point-8, and you are adding another fragment at point-10)
I have an activity hosting two fragments. The activity starts off showing a loader while it loads an object. The loaded object is then passed to both fragments as arguments via newInstance methods and those fragments are attached.
final FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.container1, Fragment1.newInstance(loadedObject));
trans.replace(R.id.container2, Fragment2.newInstance(loadedObject));
trans.commit();
The second fragment contains a android.support.v4.view.ViewPager and tabs. onResume we initialise it like follows
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(adapter.getCount()); //the count is always < 4
tabLayout.setupWithViewPager(viewPager);
The problem is android then throws
java.lang.IllegalStateException: FragmentManager is already executing
transactions
With this stack trace: (I took android.support out of the package names just for brevity)
v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:1620)
at
v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:637)
at
v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:143)
at v4.view.ViewPager.populate(ViewPager.java:1235)
at v4.view.ViewPager.populate(ViewPager.java:1083)
at
v4.view.ViewPager.setOffscreenPageLimit(ViewPager.java:847)
The data shows if setOffscreenPageLimit(...); is removed. Is there another way to avoid this issue?
When in the lifecycle is the fragment transaction complete so that I can wait to setup my pager?
Simply use childFragmentManger() for viewpager inside a Fragment
mPagerAdapter = new ScreenSlidePagerAdapter(getChildFragmentManager());
mPager.setAdapter(mPagerAdapter);
I had this exception when quickly replacing 2 fragments AND using executePendingTransactions(). Without calling this there was no exception.
What was my case?
I open a fragment A and in its onResume() (under a condition) I ask the activity to replace the fragment with fragment B. At that point the exception occurs.
My solution was to use a Handler.post(runnable) which places the query on the end of the thread queue instead of running it immediately. This way we ensure that the new transaction will be executed after any previous transactions are completed.
So my solution was as simple as:
Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(new Runnable()
{
#Override
public void run()
{
openFragmentB(position);
}
});
If you're targeting sdk 24 and above you can use:
FragmentTransaction.commitNow()
instead of commit()
If you're targeting older versions, try calling:
FragmentManager.executePendingTransactions()
after the call to commit()
I Had a similar issue,
A mainAcitivity adding fragmentA.
Then fragmentA callback mainactivity to replace itself with fragmentB.
MainActivity throw exception fragmentmanager already executing transaction when replace and commit transaction with fragmentB.
The issue actually comes from fragmentB.
I have a TabHost in fragment B which require getfragmentmanager() to add tabfragment.
Replace getfragmentmanager() by getchildfragmentmanager() in fragmentB solve the issue.
I had same problem. However, I was using the FragmentStateAdapter constructor that gets only the FragmentActivity:
package androidx.viewpager2.adapter;
public FragmentStateAdapter(
#NonNull FragmentActivity fragmentActivity
) {
this(fragmentActivity.getSupportFragmentManager(), fragmentActivity.getLifecycle());
}
Even though it gets the Lifecycle from fragmentActivity instance, it was not working at all.
Then, diving into the code of the FragmentStateAdapter, I saw there is another constructor where you can pass the Lifecycle instance.
package androidx.viewpager2.adapter;
public FragmentStateAdapter(
#NonNull FragmentManager fragmentManager,
#NonNull Lifecycle lifecycle
) {
mFragmentManager = fragmentManager;
mLifecycle = lifecycle;
super.setHasStableIds(true);
}
So, I changed my PageAdapter constructor to receive the FragmentManager as childFragmentManager and the LifeCycle from the viewLifecycleOwner. And pass those parameters to the FragmentStateAdapter constructor:
class MyPagerAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {
//...
}
Then, when calling my PagerAdapter constructor, I pass the FragmentManager and
I get the lifeCycle from the viewLifecycleOwner:
val myPagerAdapter = MyPagerAdapter(
fragmentManager = childFragmentManager,
lifecycle = viewLifecycleOwner.lifecycle
)
[UPDATE]
MyPagerAdapter is set from a Fragment.
ViewPager2
It is quite an old answer from the time of ViewPager and has helped many to solve the issue who came across this. Just in case you are having ViewPager2 and face similar issue then,
We know this happens when we have ViewPager2 inside a fragment and FragmentStateAdapter class has a constructor with Fragment parameter, so you can use that to create your adapter.
Like,
class ScreenSlidePagerAdapter(fragment: Fragment): FragmentStateAdapter(fragment)
Then,
val pagerAdapter = ScreenSlidePagerAdapter(this)
Got a similar error not connected to question but hops its helps someone when working with firebase.
Remove activity, requireActivity or this from .addSnapshotListener(), keep it blank.
videosListener = videosCollectionRef
.addSnapshotListener() { snapshot, exception ->
if (exception != null) {
Log.e("Exception", "Could not retrieve documents: $exception")
}
if (snapshot != null) {
parseData(snapshot)
}
}
If anyone is using Robolectric >3.6 and at least through 4.0.2 with a ViewPager you may see this even with correct code.
There is related information in this github issue tracking the problem for Robolectric.
The issue is not resolved as I write this answer, and the only workarounds known are to use #Config(sdk={27}) which appears to work for some but did not work for me, or implement a ViewPagerShadow with a workaround in your test package with the rather-long code referenced on github (I can include it here if that is better but this may not be relevant enough as an answer?) and use #Config(shadows={ShadowViewPager.class})
I used/extended my adapter with FragmentStatePagerAdapter instead of FragmentPagerAdapter this resolved my issue.
Use FragmentStatePagerAdapterif the fragments are dynamic and are changing frequently during run-time.
Use FragmentPagerAdapter if the fragments are static and do NOT change frequently during run-time.
enlightened from this article,
https://medium.com/inloopx/adventures-with-fragmentstatepageradapter-4f56a643f8e0
I had the same issue. Navigation from one fragment to another by add method. The problem was that I added fragment transition in the onViewCreadted method. Then I tried to move the transition in onResume method. That also does not help. So I added transition half-second after the resume. After that everything was fine.
It was a small crush that affected less than half percent of my users. Even though, it was very annoying.
Just do not call
FragmentTransaction#commit() from fragment which is already been in the same FragmentManager at another transaction process
For ex:
Activity:
override fun onCreate(savedInstanceState: Bundle?) {
val fragment = MyFragment()
setFragment(fragment)
}
fun setFragment(fragment: Fragment){
supportFragmentManager.beginTransaction()
.replace(...,fragment, ...)
.commit()
}
MyFragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
/*
* here error will occurs, because the fragment MyFragment is in current transaction
*/
activity?.setFragment(AnotherFragment())//error
}
Solution:
Do this:
Activity:
override fun onCreate(savedInstanceState: Bundle?) {
setFragment(MyFragment())
...
setFragment(AnotherFragment())
}
fun setFragment(fragment: Fragment){
supportFragmentManager.beginTransaction()
.replace(...,fragment, ...)
.commit()
}
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;
}
}
I know there is a Robolectric.shadowOf(Fragment) method and a ShadowFragment class, thought they aren't listed on the docs, but I can't make it work.
myFragment = new MyFragment();
myFragment.onCreateView(LayoutInflater.from(activity), (ViewGroup) activity.findViewById(R.id.container), null);
myFragment.onAttach(activity);
myFragment.onActivityCreated(null);
I'm working with API level 13 (Honeycomb).
Thanks.
Edit #4 & #5: In Robolectric 3.*, they split up the fragment starting functions.
For support fragments, you will need to add a dependency to your build.gradle:
testCompile "org.robolectric:shadows-supportv4:3.8"
Import: org.robolectric.shadows.support.v4.SupportFragmentTestUtil.startFragment;
For platform fragments, you don't need this dependency. Import: import static org.robolectric.util.FragmentTestUtil.startFragment;
They both use the same name of startFragment().
import static org.robolectric.shadows.support.v4.SupportFragmentTestUtil.startFragment;
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class)
public class YourFragmentTest
{
#Test
public void shouldNotBeNull() throws Exception
{
YourFragment fragment = YourFragment.newInstance();
startFragment( fragment );
assertNotNull( fragment );
}
}
Edit #3: Robolectric 2.4 has an API for support and regular fragments. You can either use the newInstance() pattern or use the constructor when constructing your Fragment's.
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertNotNull;
import static org.robolectric.util.FragmentTestUtil.startFragment;
#RunWith(RobolectricGradleTestRunner.class)
public class YourFragmentTest
{
#Test
public void shouldNotBeNull() throws Exception
{
YourFragment fragment = new YourFragment();
startFragment( fragment );
assertNotNull( fragment );
}
}
Edit #2: There's a new helper if you're using support fragments (one that supports regular activities/fragments should be in the next release):
import static org.robolectric.util.FragmentTestUtil.startFragment;
#Before
public void setUp() throws Exception
{
fragment = YourFragment.newInstance();
startFragment( fragment );
}
Edit: If you upgraded to Robolectric 2.0:
public static void startFragment( Fragment fragment )
{
FragmentActivity activity = Robolectric.buildActivity( FragmentActivity.class )
.create()
.start()
.resume()
.get();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add( fragment, null );
fragmentTransaction.commit();
}
Original answer
As the other commenter suggested, you do need to use the fragment manager (instead of calling the lifecycle methods you listed above).
#RunWith(MyTestRunner.class)
public class YourFragmentTest
{
#Test
public void shouldNotBeNull() throws Exception
{
YourFragment yourFragment = new YourFragment();
startFragment( yourFragment );
assertNotNull( yourFragment );
}
I create a test runner and have a function that starts up a fragment for me so I can use it everywhere.
public class MyTestRunner extends RobolectricTestRunner
{
public MyTestRunner( Class<?> testClass ) throws InitializationError
{
super( testClass );
}
public static void startFragment( Fragment fragment )
{
FragmentManager fragmentManager = new FragmentActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add( fragment, null );
fragmentTransaction.commit();
}
}
You guys are all doing this the hard way. Just use FragmentTestUtil.
FragmentTestUtil.startFragment(yourfragment);
Support fragments have been moved to module:
shadows-support-v4
(as of July,2015, Robolectric v3.0)
Add a gradle dependency to app/build.gradle:
testCompile 'org.robolectric:shadows-support-v4:3.0'
Then import to your Robolectric test java class:
import org.robolectric.shadows.support.v4.SupportFragmentTestUtil;
Then you can start & use a support-v4 fragment for testing:
#Test
public void minimalFragmentTest() throws Exception {
MyFunFragment fragment = new MyFunFragment();
SupportFragmentTestUtil.startVisibleFragment(fragment);
assertThat(fragment.getView()).isNotNull();
}
References:
github changelog, moving support fragments to different module
Old android fragments are already deprecated, seems like support fragments soon will be deprecated too. To test androidx fragments you can use fragment scenarios with robolectric https://developer.android.com/training/basics/fragments/testing
testImplementation 'androidx.fragment:fragment-testing:1.2.2'
val scenario = launchFragmentInContainer<MyFragment>()
scenario.onFragment { fragment ->
assertNotNull(fragment.view.synteticInflatedView)
}
I'm pretty sure you have to create a FragmentTransaction using the FragmentManager, then it will work.
I just wanted to add that in Robolectric 2.0 even after doing:
activity = Robolectric.buildActivity(FragmentActivity.class).create().start().resume().get();
fragment.show(activity.getSupportFragmentManager(), null);
fragment.getDialog(); //This stills returns null
It still returned null for me. what I did was to add activity.getSupportFragmentManager().executePendingTransaction(); and it worked.
It seems robolectric doesn't run this for some reason. it seems that maybe the Looper is paused or something. any way this worked for me and it looks like this:
activity = Robolectric.buildActivity(FragmentActivity.class).create().start().resume().get();
fragment.show(activity.getSupportFragmentManager(), null);
activity.getSupportFragmentManager().executePendingTransactions();
fragment.getDialog();
SupportFragmentTestUtil.startFragment(fragment, AppCompatActivity::class.java)
If the activity is extending AppCompatActivity
This is using Kotlin