ResultReceiver doesn't survire to screen rotation
Asked Answered
H

6

7

I am implementing a REST client in Android. I have seen an example of using a Service to perform the connection to the server and the ResultReceiver to be notified of the operation completion. I am calling the service from a fragment and, if I try to rotate the screen while the service is running, the getActivity() method in ResultReceiver returns null because probably that fragment is not in layout anymore.

The callback method in the fragment:

@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
    Response response = (Response) resultData
            .getSerializable(RestService.RESULT);
    if (resultCode == RestService.SUCCESS
            && response != null) {
        if (getActivity() != null) {
            recommendationResponse = response;
            getLoaderManager().restartLoader(0, new Bundle(),
                    Fragment.this);
        }

    }
}

The getActivity() returns null. Is this normal? What approach could I use to allow notification even on screen rotation? Local Broadcast?

Hobnob answered 7/6, 2012 at 7:16 Comment(0)
H
1

I am using a BroadcastReceiver registered using LocalBroadcastManager and it is working properly. It wasn't so simple. Does a better solution exist?

Hobnob answered 7/6, 2012 at 13:23 Comment(0)
D
13

No,

android:configChanges="orientation"

is not a solution.

To use ResultReceiver I:

  • save it on orientation changes:

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putParcelable(Consts.RECEIVER, mReceiver);
        super.onSaveInstanceState(outState);
    }
    
  • reset the receiver:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    
        if (savedInstanceState != null) {
            mReceiver = savedInstanceState.getParcelable(Consts.RECEIVER);
        }
        else {
            mReceiver = new MyResultReceiver(new Handler());
        }
        mReceiver.setReceiver(this);
    }
    

Here is my ResultReceiver class:

import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;

public class MyResultReceiver extends ResultReceiver {
    private Receiver mReceiver;

    public MyResultReceiver(Handler handler) {
        super(handler);
    }

    public void setReceiver(Receiver receiver) {
        mReceiver = receiver;
    }

    public interface Receiver {
        public void onReceiveResult(int command, Bundle resultData);
    }

    @Override
    protected void onReceiveResult(int command, Bundle resultData) {
        if (mReceiver != null) {
            mReceiver.onReceiveResult(command, resultData);
        }
    }
}
Dye answered 14/4, 2014 at 11:47 Comment(5)
Brilliant! I saw many other similar questions (with many upvotes) related to ResultReceiver, and any of them addressing successfully such a frequent scenario as activity recreation.Ginder
How do you avoid a ClassCastException in onCreateView() when calling getParcelable()? Doesn't getParcelable() return a ResultReceiver instance (not an instance of your subclass) because your subclass doesn't implement Parcelable.CREATOR?Decemvir
@Decemvir With my testing, when you rotate the device, the above method works. However, if android shuts down the app in the background and you start it again, I get the ClassCastException in onCreateView(). I am not sure why but this is what I have found from my testing...Printmaker
@MicroR I would guess this is because on rotation the Bundle does not get serialized to a Parcel, it stays in memory and so you get the original object back out. If the app is terminated (memory pressure) the Bundle is likely turned into a Parcel which is where you run into issues re-extracting the object because the Parcel will only return a base ResultReceiver. I still have not found a good solution.Decemvir
@Decemvir Indeed! That makes sense. I opened a question here (stackoverflow.com/q/37439838/3075340) but is it possible to make the custom ResultReceiver implement parcelable or no..?Printmaker
H
1

I am using a BroadcastReceiver registered using LocalBroadcastManager and it is working properly. It wasn't so simple. Does a better solution exist?

Hobnob answered 7/6, 2012 at 13:23 Comment(0)
S
1

I think I stumbled upon the same issue and resolved it by verifying for NULL in the onReceivedResult method of my ResultReceiver. The code posted here works on a worker fragment (fragment without UI and setRetainInstance(true) in onCreate)

protected void onReceiveResult(int resultCode, Bundle resultData) {
            //Verify activity
            if(getActivity() != null){
                //Handle result
            }else{
                notificationPending = true;                 
            }
        }

The notificationPending flags helps the fragment hold the pending notification if the activity was not found (Activity is not available on fragment Detach).

When the fragment reattaches to the activity i perform this logic

public void onAttach(Activity activity){
    super.onAttach(activity);
        if(notificationPending){
            //Handle notification
            notificationPending = false;
        }
}

Hope it helps. You can ask for further details if you like. Cheers

Slipslop answered 29/9, 2012 at 19:10 Comment(0)
P
0

Yes, this normal since the ResultReceiver might be "headless".

I tried saving the ResultReceiver at onSaveInstanceState(), but it didn't work, since updates, that happen while the receiving Fragment is destroyed, get lost, and the references to callbacks too.

An explanation and a possible solution can be found here: https://stanmots.blogspot.com/2016/10/androids-bad-company-intentservice.html

Another good read concerning this problem: https://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html

My full solution how to use a ResultReceiver can be found here: https://mcmap.net/q/303696/-how-to-receive-data-from-service-when-fragment-is-resumed

Passed answered 23/1, 2019 at 20:30 Comment(0)
S
0

in my case found another approach that works, maybe it will help someone too

  @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);//add this line

        
    }
Stull answered 13/3, 2024 at 19:35 Comment(0)
T
-1

The getActivity() returns null. Is this normal?

Android Activities are recreated after device rotation.

After activity is recreated it does not holds old context.that's why your getting getActivity() as null

What approach could I use to allow notification even on screen rotation? Local Broadcast?

If you dont want activity to recreated on screen rotation.mention following in manifest

        <activity
            android:name=".MyActivity"
            android:configChanges="orientation"    <<<<<<<<<
            android:label="@string/app_name"
            android:screenOrientation="portrait" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

And last You will have to override following in Activity.

@Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        // TODO Auto-generated method stub
        super.onConfigurationChanged(newConfig);
    }
Trenchant answered 7/6, 2012 at 7:54 Comment(1)
Thanks for the answer. Actually I would prefer to not set the screenOrientation on manifest because the layout is different in landscape mode so I cannot set that.Hobnob

© 2022 - 2025 — McMap. All rights reserved.