progNewbie
progNewbie

Reputation: 4822

java.lang.IllegalStateException: Fragment not attached to a context

I have a tablayout with a viewpager in my MainActivity.

My PagerAdapter looks like this:

public class MainActivityPagerAdapter extends PagerAdapter {

    public MainActivityPagerAdapter(FragmentManager fm, int numOfTabs) {
        super(fm, numOfTabs);
    }

    @Override
    public Fragment getItem(int position) {

        switch (position) {
            case 0:
                return new StoresFragment();
            case 1:
                return new OrdersFragment();
            default:
                return null;
        }
    }
}

I am coming back from another activity like this:

Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish(); //finishAffinity();

But then I get an java.lang.IllegalStateException in one of my Fragments in the viewpager of the MainActivity.

I read many related questions and tried to solve this. It is said, that this happens when one keeps references to Fragments outside of the PagerAdapter. But I am not doing this, as you can see in my code.

Does anyone know what I am doing wrong?

Edit - Stacktrace

FATAL EXCEPTION: main
    Process: com.lifo.skipandgo, PID: 23665
    java.lang.IllegalStateException: Fragment OrdersFragment{42c2a740} not attached to a context.
    at android.support.v4.app.Fragment.requireContext(Fragment.java:614)
    at android.support.v4.app.Fragment.getResources(Fragment.java:678)
    at com.lifo.skipandgo.activities.fragments.OrdersFragment$1.results(OrdersFragment.java:111)
    at com.lifo.skipandgo.connectors.firestore.QueryResult.receivedResult(QueryResult.java:37)
    at com.lifo.skipandgo.controllers.UserController$2.onUpdate(UserController.java:88)
    at com.lifo.skipandgo.connectors.firestore.QuerySubscription.onEvent(QuerySubscription.java:59)
    at com.lifo.skipandgo.connectors.firestore.QuerySubscription.onEvent(QuerySubscription.java:18)
    at com.google.firebase.firestore.zzg.onEvent(Unknown Source)
    at com.google.firebase.firestore.g.zzh.zza(SourceFile:28)
    at com.google.firebase.firestore.g.zzi.run(Unknown Source)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:146)
    at android.app.ActivityThread.main(ActivityThread.java:5653)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
    at dalvik.system.NativeStart.main(Native Method)

Edit: Interesting is, that the view has defenitely loaded when the error occurs. Because the error occurs about 10-15 seconds later after the fragment is shown again. I this in my orderFragment, where the error occurs:

orders = new QueryResult<UserOrder>(UserOrder.class) {
            @Override
            public void results(List<UserOrder> results) { 
orderLoadingMessage.setBackgroundColor(getResources().getColor(R.color.green)); 
    }
}

I do this in onCreateView and this result comes about 10-15 seconds after the view loaded.

Upvotes: 6

Views: 34960

Answers (7)

Maddy Sharma
Maddy Sharma

Reputation: 4956

Use this before update your Activity UI :

if(isAdded())// This {@link androidx.fragment.app.Fragment} class method is responsible to check if the your view is attached to the Activity or not
{
        // TODO Update your UI here
}

Upvotes: 8

Abhijeet Prusty
Abhijeet Prusty

Reputation: 101

viewPager.offscreenPageLimit = (total number of fragments - 1)
viewPager.adapter = Adapter

Use this if your are using viewpager And if you are using bottom navigation just simply check if(context != null) But i suggest to use max 3 fragments in offscreenPageLimit

Upvotes: 4

Donayam Nega
Donayam Nega

Reputation: 11

I had similar problem. I have solved it by following ferini's recommendation. I was using a live data which was firing before the context was attached.

Here is my full implementation

public class PurchaseOrderFragment extends Fragment {


    FragmentPurchaseOrderBinding binding;
    CurrentDenominationViewModel currentDenominationViewModel;
    @Inject
    ViewModelFactory viewModelFactory;
    CurrentVoucherChangedObserver observer;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        currentDenominationViewModel = new ViewModelProvider(requireActivity(),viewModelFactory).get(CurrentDenominationViewModel.class);
observer =  new CurrentVoucherChangedObserver();

    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_purchase_order, container, false);
        return binding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        
        currentDenominationViewModel.getCurrentVoucherStatisticsLiveData().observe(requireActivity(), observer);
    }

    @Override
    public void onAttach(@NonNull Context context) {
        AndroidSupportInjection.inject(this);
        super.onAttach(context);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        currentDenominationViewModel.getCurrentVoucherStatisticsLiveData().removeObserver(observer);
    }

    final  class CurrentVoucherChangedObserver implements Observer<VoucherStatistics> {
        @Override
        public void onChanged(VoucherStatistics x) {
            String denomination = x.getDenomination()+"";
            binding.tvDenomination.setText(denomination);

            String stockAmount = requireContext().getResources().getString(R.string.StockAmount);
            String text= "("+String.format(stockAmount,x.getQuantity()+"")+")";
            binding.tvInStock.setText(text);
        }
    }

}

Upvotes: 1

CoolMind
CoolMind

Reputation: 28809

In my case this exception happened when I showed a DialogFragment and called it's methods. Because the fragment hasn't attached to a FragmentManager (this operation completes asynchronously) before calling methods, an application crashed.

val fragment = YourDialogFragment.newInstance()
fragment.show(fragmentManager, YourDialogFragment.TAG)

// This will throw an exception.
fragment.setCaptions("Yes", "No")

If you add the fragment with FragmentManager, you will get another exception: java.lang.IllegalArgumentException: No view found for id 0x1020002 (android:id/content) for fragment (or similar, if you use another id).

You can call fragment methods via post (or postDelayed), but it is a bad solution:

view?.post {
    fragment.setCaptions("Yes", "No")
}

Currently I use childFragmentManager instead of fragmentManager:

val fragment = YourDialogFragment.newInstance()
fragment.show(childFragmentManager, YourDialogFragment.TAG)
fragment.setCaptions("Yes", "No")

I don't remember what I did, but now it works.

Upvotes: 2

Muhammad Muzammil
Muhammad Muzammil

Reputation: 1439

Some of your callbacks are being fired after your fragment is detached from activity. To resolve this issue you need to check whether your fragment is added before acting upon any callbacks. For example, change your orders object's initialization to this:

orders = new QueryResult<UserOrder>(UserOrder.class) {
            @Override
            public void results(List<UserOrder> results) { 
                if(isAdded()) {
                    orderLoadingMessage.setBackgroundColor(
                        getResources().getColor(R.color.green)); 
                }
            }
        }

Upvotes: 3

ferini
ferini

Reputation: 1958

The problem seems to be, that your fragment is listening to some events (via UserController and QueryResult) and these are fired before the fragment is attached to context.

Try to unregister the fragment when it becomes detached and to them again after attaching (LiveData can also help with this). Another way could be to receive and store the event while detached and only process it after attaching.

Upvotes: 8

Vishnu Magar
Vishnu Magar

Reputation: 66

Your Solution

change your getItem() method to

switch (position) {
        case 0:
            return new StoresFragment();
        case 1:
            return new OrdersFragment();
        default:
            return null;
    }

Upvotes: -1

Related Questions