Calling Google Play Game Services from a fragment

2020-02-06 09:46发布

I have implemented some Google Play Game Services features in my Android app as a separate Activity and I am now trying to rewrite my code as an (Action Bar Sherlock) fragment. I am using the supplied GameHelper code in my fragment.

An auto sign in works correctly. A user initiated sign in fails because the StartResolutionForResult call in GameHelper returns to the Activity's onActivityResult rather than to the fragment. I have verified all this by using Log.D . My understanding of this is limited - what should I do to fix this ? I have tried passing a different context but the StartResolutionForResult seems to only accept an Activity as its context.

4条回答
Luminary・发光体
2楼-- · 2020-02-06 10:19

The Google Play game services API should be tied to an Activity's lifecycle, not a Fragment's lifecycle. If your game logic is in a Fragment, you can implement onActivityResult on the Activity and call your Fragment from there. Take a look at our Type A Number Challenge sample, which, apart from being a highly exciting and addictive game</sarcasm>, demonstrates how to deal with Fragments. Each screen in Type A Number is a fragment, and they communicate with the Activity as needed.

In this particular case, all the interaction with the games API is made by the Activity. However, you could just as well make the Activity hand the GamesClient object to the Fragment so that it could implement its own logic.

In all cases, remember not to keep a persistent reference to GamesClient in the Fragment for longer than you need. It's probably best to query it from the Activity (via an interface, for instance) whenever you need it. This is to prevent it from leaking during the Activity's lifecycle.

查看更多
Bombasti
3楼-- · 2020-02-06 10:19

You can forward the onActivityResult call to the fragment like this:

We should bitwise shift a request code by 16 bits.

public static final int REQUEST_CHECK_SETTINGS = 1<<16; //shifted 1 16 bits

Add this to the activity who owns the fragment.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
}

Note: I've figured this out from the source code of onActivityResult in FragmentActivity. It is shifting the requestCode 16 bits to the right.

/**
 * Dispatch incoming result to the correct fragment.
 */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    mFragments.noteStateNotSaved();
    int index = requestCode>>16;
    if (index != 0) {
        index--;
        final int activeFragmentsCount = mFragments.getActiveFragmentsCount();
        if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) {
            Log.w(TAG, "Activity result fragment index out of range: 0x"
                    + Integer.toHexString(requestCode));
            return;
        }
        final List<Fragment> activeFragments =
                mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount));
        Fragment frag = activeFragments.get(index);
        if (frag == null) {
            Log.w(TAG, "Activity result no fragment exists for index: 0x"
                    + Integer.toHexString(requestCode));
        } else {
            frag.onActivityResult(requestCode&0xffff, resultCode, data);
        }
        return;
    }

    super.onActivityResult(requestCode, resultCode, data);
}

Note 2: I would be glad if someone could tell me why using this method would be a bad approach

查看更多
我命由我不由天
4楼-- · 2020-02-06 10:30

Shifting 1<<16 is not supported anymore.

You can try call it from your Fragment

private static final int REQUEST_CHECK_SETTINGS = 0x1;

final ResolvableApiException rae = (ResolvableApiException) e;      
startIntentSenderForResult(rae.getResolution().getIntentSender(), REQUEST_CHECK_SETTINGS, null, 0, 0, 0, null);

And catch result on Fragment's onActivtyResult like this:

 @Override
    public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            // Check for the integer request code originally supplied to startIntentSenderForResult().
            case REQUEST_CHECK_SETTINGS:
                switch (resultCode) {
                    case Activity.RESULT_OK:
                        // Do smth
                        break;
                    case Activity.RESULT_CANCELED:
                        // show Error
                        break;
                }
                break;
        }
    }
查看更多
家丑人穷心不美
5楼-- · 2020-02-06 10:35

I'm not sure how similar the API for Games is compared to Nearby (I think this should work), but when creating a GoogleApiClient for Nearby, I found that the objects passed in error callbacks (Status, ConnectionResult) are conveniently Parcelable. So I created an Activity that can be launched to handle these errors, and then pass whatever result it receives back to the caller.

https://gist.github.com/damien5314/c13ce47bca035c517dfbdfb2af488a73

Usage:

Nearby.Messages.publish(mNearbyApi, mMessage)
        .setResultCallback(new ResultCallback<Status>() {
            @Override
            public void onResult(@NonNull Status status) {
                Log.d(TAG, "Nearby publish result: " + getStatusCodeString(status.getStatusCode()));
                if (status.getStatusCode() > 0) {
                    Intent intent = ResolveErrorActivity.buildIntent(getContext(), status);
                    startActivityForResult(intent, ResolveErrorActivity.REQUEST_RESOLVE_ERROR);
                }
            }
        });

As long as you call startActivityForResult from a Fragment, it should get the result as usual.

查看更多
登录 后发表回答