en / de
AI
Expertisen
Methoden
Dienstleistungen
Referenzen
Jobs & Karriere
Firma
Technologie-Trends TechCast WebCast TechBlog News Events Academy

Tackling the notorious IllegalStateException with the help of an Event Bus

by it support 30. November 2015· 2 Min. lesen

My colleague Fabian Mächler wrote a great post about Greenrobot’s EventBus and how it can be used to simplify communication among parts of an Android application. You can read about it here.  An event bus can have an additional benefit, however. It can be used to deal with the pesky “IllegalStateException: Can not perform this action after onSaveInstanceState()” problem. This exception occurs when a fragment transaction takes place after a fragment’s state has been saved. You can read all about it here.

In a recent project of mine I ran into this problem when trying to commit fragment transactions from asynchronous callbacks. If you read the above article you might have noticed that performing fragment transactions inside asynchronous callbacks is one of the things you shouldn’t do. Sometimes, however, it makes sense to perform a fragment transaction in an asynchronous callback. In our project that was the case and we initially implemented our requests and callbacks like this:

Inside a button listener or listener of another UI element, we performed a request which accepted a listener as its parameter.

view.findViewById(R.id.pressMeButton).setOnClickListener(new View.OnClickListener() {
    
    @Override
    
    public void onClick(View v) {
        
        new DataRepository(getActivity()).
            performRequest(new Repository.RepositoryListener() {

                @Override
            
                public void onSuccess() {
                
                    mListener.onShowDetailView();
            
                }
        
            });
    
     }
});

The signature of the request method looked something like this:

void performRequest(final RepositoryListener listener);

When the request finished, it called the listener’s onSuccess() method. Inside the onSuccess() method we performed a fragment transaction:

@Override

public void onShowDetailView() {
    
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    
ft.replace(R.id.fragmentContainer, DetailFragment.getInstance());
    
ft.addToBackStack(null);
ft.commit();

}

This worked well until we hit the home button while the request was still running. After we hit the home button the app crashed once the request returned with the following exception:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

The problem

The technical reason why this exception is thrown is because the fragment transaction takes place after the fragment’s state has been saved. If Android wouldn’t throw the exception, your app would experience UI state loss. The root cause of the problem is that we are performing fragment transactions inside asynchronous callbacks. We cannot control when asynchronous callbacks are called and thus we cannot guarantee that they will be called when our current fragment or activity is still running.

The solution

The article on the Android Design Patterns blog says that we shouldn’t perform fragment transactions inside asynchronous callbacks. That’s not an acceptable solution though. So how can we avoid fragment state loss while still performing the transactions inside asynchronous callbacks? A great solution to this problem is to use a message-oriented approach. Our request method would look like this:

void performRequest();

We do away with the Listener argument. Inside our implementation of the method we no longer call a listener’s onSuccess() method when we receive a result. Instead, we post an event (or a message, if you will) to the event bus. However, we only do this if the calling fragment is not paused. If the fragment has been paused while the request has been running, we buffer the event.

@Override
public void pause() {
    mPaused = true;
}
@Override
public void performRequest() {
    //Perform request on another thread...

    //Post result to main thread
    postEventIfNotPaused(new DataFetchedEvent());
}
protected void postEventIfNotPaused(DataFetchedEvent event) {
    if(!mPaused) {
        EVENT_BUS.post(event);
    } else {
        mBufferedEvents.add(event);
    }
}

Once the fragment resumes, we send all the buffered events onto the event bus.

@Override
public void resume() {
    mPaused = false;
    sendBufferedEvents();
}

private void sendBufferedEvents() {
    while(mBufferedEvents.size() > 0){
        final DataFetchedEvent bufferedEvent = mBufferedEvents.elementAt(0);
        mBufferedEvents.removeElementAt(0);
        EVENT_BUS.post(bufferedEvent);
    }
}

The fragment now just needs to call the repository’s pause() and resume() methods in its respective onPause() and onResume() methods. This approach prevents you from having to write any sort of fragile boilerplate to deal with lifecycle issues and it cleanly takes care of the state loss problem. Furthermore, it also doesn’t simply ignore any results that come in while the fragment is paused. Instead it buffers them ensuring that all results are delivered to the UI of your app.

A sample project that demonstrates this is up on Github.

Kommentare

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Newsletter - aktuelle Angebote, exklusive Tipps und spannende Neuigkeiten

 Jetzt anmelden

Copyright © 2025 Noser Engineering AG – Alle Rechte vorbehalten.

NACH OBEN
Privacy Policy Cookie Policy
Zur Webcast Übersicht