5

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.

progNewbie
  • 2,640
  • 6
  • 33
  • 78

7 Answers7

7

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.

ferini
  • 1,233
  • 10
  • 11
  • Ok, then what comes to my mind is if you don't actually have two instances of the same fragment. One is attached and works ok, but the old one is detached and still receives the result. – ferini Aug 01 '18 at 06:59
  • I also thought about that. But how could I assure, that the detached one is not receiving the result anymore? – progNewbie Aug 01 '18 at 09:11
  • 1
    Well, I do not know how you register for your events, but you wrote that you do it in `onCreateView`, so then easiest would be to unregister from them in `onDestroyView`. – ferini Aug 01 '18 at 09:19
3
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

3

Use this before eupdating 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
}
Maddy
  • 4,402
  • 2
  • 27
  • 38
2

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)); 
                }
            }
        }
Muhammad Muzammil
  • 864
  • 1
  • 9
  • 24
1

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.

CoolMind
  • 16,738
  • 10
  • 131
  • 165
0

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);
        }
    }

}

  • 1
    Hello! While this code may solve the question, [including an explanation](https://meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – Brian Jun 25 '20 at 18:40
-1

Your Solution

change your getItem() method to

switch (position) {
        case 0:
            return new StoresFragment();
        case 1:
            return new OrdersFragment();
        default:
            return null;
    }
  • As I explained in my comments I had it like this in the beginning. I also even changed my code in the question to this. – progNewbie Jul 19 '18 at 12:08