I have been trying to implement Google Play Services into my LibGDX game, and have been using the real-time Multiplayer APK.
However, after everyone has joined the room through auto-matching, I try to start the game through calling a method to change the screen, but i get the error as followed. Even if i removed the contents of the method, the same error still occurs. Could anyone enlighten me?
Thank you!
The error logged in the console is ,
java.lang.RuntimeException: Failure delivering result
ResultInfo{who=null, request=10002, result=-1, data=Intent { (has
extras) }} to activity
{com.mygdx.game/com.mygdx.game.AndroidLauncher}:
java.lang.NullPointerException
Caused by: java.lang.NullPointerException
at com.mygdx.game.GSGameHelper.onActivityResult(GSGameHelper.java:76) -->
which points to this.game.multiplayerready()
Code as Follows:
public void onActivityResult(int request,int response, Intent data){
if (request == GSGameHelper.RC_WAITING_ROOM){
if (response == Activity.RESULT_CANCELED || response == GamesActivityResultCodes.RESULT_LEFT_ROOM ){
Games.RealTimeMultiplayer.leave(getApiClient(), this, mRoomID);
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
BaseGameUtils.showAlert(activity, "Left Room");
}else{
BaseGameUtils.showAlert(activity, "Game Starting!");
this.game.multiplayerGameReady();
}
}
else if (request == GSGameHelper.RC_SELECT_PLAYERS){
if (response != Activity.RESULT_OK) {
// user canceled
return;
}
// get the invitee list
Bundle extras = data.getExtras();
final ArrayList<String> invitees =
data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS);
// get auto-match criteria
Bundle autoMatchCriteria = null;
int minAutoMatchPlayers =
data.getIntExtra(Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);
int maxAutoMatchPlayers =
data.getIntExtra(Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);
Gdx.app.log("J", "Jmin" + minAutoMatchPlayers + " Jmax:" + maxAutoMatchPlayers);
for (String invitee : invitees){
Gdx.app.log("L" , invitee);
}
if (minAutoMatchPlayers > 0) {
autoMatchCriteria = RoomConfig.createAutoMatchCriteria(
minAutoMatchPlayers, maxAutoMatchPlayers, 0);
} else {
autoMatchCriteria = null;
}
// create the room and specify a variant if appropriate
RoomConfig.Builder roomConfigBuilder = makeBasicRoomConfigBuilder();
roomConfigBuilder.addPlayersToInvite(invitees);
if (autoMatchCriteria != null) {
roomConfigBuilder.setAutoMatchCriteria(autoMatchCriteria);
}
RoomConfig roomConfig = roomConfigBuilder.build();
Games.RealTimeMultiplayer.create(getApiClient(), roomConfig);
// prevent screen from sleeping during handshake
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}else{
super.onActivityResult(request, response, data);
}
}
public class MacroHardv2 extends ApplicationAdapter {
public void multiplayerGameReady(){
//gamew.multiplayer = true;
//Gdx.app.log("EMPEZANDO", "Starting Game");
//gsm.set(new PlayState(gsm));
//dispose();
}
}
This is where i initiate the class
public class AndroidLauncher extends AndroidApplication implements ActionResolver{
private GSGameHelper _gameHelper;
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_gameHelper = new GSGameHelper(this, GameHelper.CLIENT_GAMES);
_gameHelper.enableDebugLog(false);
GameHelperListener gameHelperListerner = new GameHelper.GameHelperListener() {
#Override
public void onSignInSucceeded() {
// TODO Auto-generated method stub
}
#Override
public void onSignInFailed() {
// TODO Auto-generated method stub
}
};
_gameHelper.setup(gameHelperListerner);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();config.useImmersiveMode = true;
initialize(new MacroHardv2(this), config);
}
And The class constructor is as follows
public MacroHardv2(ActionResolver actionResolver) {
this.actionResolver = actionResolver;
actionResolver.setGame(this);
}
You are trying to call
this.game.multiplayerGameReady();
But you probably didn't set "this.game" anywhere. Where did you define your "game" object. Can you please show the code block that you define it and also set it, or instantiate it.
So "this.game" is your null object you trying to use.
Related
I am very new to flutter+dart framework. I am trying to understand how EventChannel works. I have set up EventChannel to capture the number of an incoming call.
On the android side, I have set up an BroadcastReceiver as follows.
public class CallEventHandler extends BroadcastReceiver implements EventChannel.StreamHandler {
private static final String TAG = "[SAMPLE]";
private static final int NUMBER_LEN = 10;
private EventChannel.EventSink eventSink = null;
private Activity activity = null;
public CallEventHandler(Activity activity) {
this.activity = activity;
}
#Override
public void onListen(Object arguments, EventChannel.EventSink events) {
Log.i(TAG, "[onListen] setting up events");
eventSink = events;
}
#Override
public void onCancel(Object arguments) {
Log.i(TAG, "[onCancel] cancel events");
eventSink = null;
activity = null;
}
#Override
public void onReceive(Context context, Intent intent) {
try {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
if(state.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
if(incomingNumber != null) {
Log.i(TAG, "[CallEventHandler] Incoming number : " + incomingNumber);
if(incomingNumber.length() > NUMBER_LEN) {
incomingNumber = incomingNumber.substring(incomingNumber.length() - NUMBER_LEN, incomingNumber.length());
Log.i(TAG, "[CallEventHandler] Incoming number after : " + incomingNumber);
if(activity != null) {
String finalIncomingNumber = incomingNumber;
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
if(eventSink != null) {
Log.i(TAG, "[CallEventHandler] HERESSSSS : " + finalIncomingNumber);
eventSink.success(finalIncomingNumber);
}
}
});
}
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
In the onReceive method, I am getting the incoming number and I am sending it to EventSink.
In my MainActivity I am setting up the CallEventHandler as follows:
private final String eventId = "SAMPLE_ID";
private CallEventHandler handler = new CallEventHandler(this);
#Override
public void onStart() {
super.onStart();
...
registerReceiver(handler, filter);
}
#Override
public void onStop() {
super.onStop();
unregisterReceiver(handler);
}
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), eventId)
.setStreamHandler(handler);
}
On the Flutter side, the code is as follows:
class EventHandler {
static const String TAG = "[SAMPLE]";
final String _eventId = "SAMPLE_ID";
EventChannel? _evtChannel;
Stream<String>? _evtStream;
EventHandler() {
debugPrint(TAG + " Setting up EventHandler");
_evtChannel = EventChannel(_eventId);
_evtStream = _evtChannel?.receiveBroadcastStream().distinct().map((dynamic
event) => getString(event as String));
}
void startListening(void Function(String data)? onData) {
debugPrint(TAG + " starting listening");
_evtStream?.listen((data) {
debugPrint(TAG + " In listening");
onData!(data);
});
}
}
In my UI code, I have a StatefulWidget (MySamplePage) where I am registering my callback when the call is received
void initState() {
widget.handler.startListening((incomingNumber) {
debugPrint(_tag + " data : $incomingNumber");
...
});
}
In my stateful home page build method, I initialize the handler in initState and added a route in build method
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
super.initState();
debugPrint(_tag + "initState");
_handler = EventHandler();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/caller': (context) => MySamplePage(
handler: _handler
),
},
...
);
}
}
The issue I am facing is that, when the widget is opened I am receiving the first incoming call, as expected. But if I make another call, then that second call is not captured by the stream. If I press the back button, and reopen the Widget everything works as expected, the first incoming call is printed in the console. I know the the Android code is sending the event from the onReceive method (The `HERESSSSS' line is printed every time), but the flutter stream is not getting the values. I am not sure what I am doing wrong here. Can anyone please help?
My log is
I/flutter (11836): [SAMPLE][HomeScreen]initState
I/flutter (11836): [SAMPLE][EventHandler] Setting up EventHandler
V/AutofillManager(11836): requestHideFillUi(null): anchor = null
I/flutter (11836): [SAMPLE][EventHandler] starting listening
I/[SAMPLE] (11836): [onListen] setting up events
I/[SAMPLE] (11836): [CallEventHandler] Receiver start
I/[SAMPLE] (11836): [CallEventHandler] Receiver start
I/[SAMPLE] (11836): [CallEventHandler] Incoming number : +91XXXXXXXXXX
I/[SAMPLE] (11836): [CallEventHandler] Incoming number after : XXXXXXXXXX
I/[SAMPLE] (11836): [CallEventHandler] HERESSSSS : XXXXXXXXXX
I/flutter (11836): [SAMPLE][EventHandler] In listening
I/flutter (11836): [SAMPLE] data : XXXXXXXXXX
In the subsequent incoming calls, the last line is not printed
Thank you
Ok, I have managed to resolve it, but don't know if this is the correct approach. The issue is that MySamplePage is a StatefulWidget, And I am calling setState in its State object. That might be the reason it's unable to listen to the stream anymore. I have called startListening is the setState method and changed the code accordingly (remove the previous subscription and re-listen to the stream)
void startListening(void Function(String data)? onData) {
debugPrint(TAG + " starting listening");
if(_subscription != null) {
_subscription?.cancel();
_subscription = null;
}
_subscription ??= _evtStream?.listen((data) {
debugPrint(TAG + " In listening");
onData!(data);
});
}
Here _subscription is a variable of type StreamSubscription<String>?. Hope this answer is helpful. And I should have posted complete code earlier.
So currently, I'm capturing an image and updating it in a RecyclerView using the Camera Intent:
private void cameraIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
Log.e(TAG, ex.getLocalizedMessage());
}
if (photoFile != null) {
Uri uri = FileProvider.getUriForFile(getActivity().getApplicationContext(),
"packagename.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
What happens prior to this is it'll trigger the intent from an setOnItemClickListener interface I have created within my RecyclerView.Adapter which is called in onCreate to populate the data from the web-server (or when triggered by setVisibleUserHint as they re-enter the fragment again).
//init camera data
if (isCamera) {
cameraArray = object.getJSONArray(PROFILE_DETAILS_CAMERA_ARRAY_KEY);
sortAdapter(true, object, cameraArray);
} else {
galleryArray = object.getJSONArray(PROFILE_DETAILS_GALLERY_ARRAY_KEY);
sortAdapter(false, object, galleryArray);
}
//settings adapters
cameraAdapter = new RecyclerViewAdapterGallery(getActivity(), array, true);
cameraAdapter.setOnItemClickListener(new RecyclerViewAdapterGallery.onItemClickListener() {
#Override
public void setOnItemClickListener(View view, final int position, String image, final boolean isCamera, boolean isLongClick) {
clickResponse(view, position, image, isCamera, isLongClick, cameraAdapter, array, object);
}
});
recyclerView.setAdapter(cameraAdapter);
cameraAdapter.notifyDataSetChanged();
What happens post is within the onActivityResult:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_TAKE_PHOTO:
if (resultCode != Activity.RESULT_CANCELED) {
if (resultCode == Activity.RESULT_OK) {
try {
handleBigCameraPhoto(finalPosition);
} catch (IOException e) {
Log.e(TAG, e.getLocalizedMessage());
}
}
}
break;
...
}
}
handleBigCameraPhoto:
private void handleBigCameraPhoto(int position) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
getActivity().sendBroadcast(mediaScanIntent);
saveFile(f, false, position);
}
This works perfectly, it saves the file fine to the web-server but I want to update the adapter when that is successful, and of course I'm unable to restore the adapter object using outState or inState bundle.
cameraArray.put(position, parseFile.getUrl());
userObject.put(PROFILE_DETAILS_CAMERA_ARRAY_KEY, cameraArray);
userObject.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
if (cameraAdapter != null) {
cameraAdapter.updatePosition(cameraArray, position);
}
} else {
Log.e(TAG, "failed " + e.getLocalizedMessage());
}
}
});
By this point I'm not sure why the cameraAdapter isn't updating as it's not returning null and is calling updatePosition().
public void updatePosition(JSONArray array, int position) {
Log.e(TAG, "updatePositionCalled");
this.imageList = array;
notifyItemChanged(position);
}
If anyone can help me solve this mystery, that would be great! Also, if you need any more code or verification at all, let me know.
Note: I currently have the JSONArray of objects, position in the adapter and web-server object stored in the saveInstanceState bundle and is restored correctly (because when I come out of the ViewPager fragment and come back in, thus calling setUserVisibleHint it restores the adapter from the web-server correctly).
Update: I've created a getImageList() method inside the adapter and calling that after the supposed 'update', it's updating the list values but not the list?! So i really do think the problem is with notifyItemChanged(position)
public JSONArray getImageList() {
return imageList;
}
// new call
if (e == null) {
cameraAdapter.updatePosition(cameraArray, position);
Log.e(TAG, cameraAdapter.getImageList().toString());
} else {
Log.e(TAG, "failed " + e.getLocalizedMessage());
}
It literally prints out the corresponding values in the list, that has been passed to the adapter, but doesn't seem to update the UI.
Update II:
I've had two (now deleted) answers advising me to notifyDataSetChanged(), Which makes no difference at all and is counter-productive as it'll rebind the whole adapter within the fragment, thus making it slow. I'm already rebinding the dedicated position (supposedly) with notifyItemChanged().
Note II: I'm offering a bounty, not for lazy and unresearched answers but for a solution with the very least an explanation, I'd like to know why it's going wrong, so I don't run into the same problem again (not a quick fix). I'm already well aware of the different functionalities and components of a RecyclerView, RecyclerView.Adapter and RecyclerView.ViewHolder, I'm just having trouble in this particular scenario where the Activity is returning a result, but not updating the UI as it should.
Hey i think the issue is with the line this.imageList = array of below method,
public void updatePosition(JSONArray array, int position) {
Log.e(TAG, "updatePositionCalled");
this.imageList = array;
notifyItemChanged(position);
}
Reason :
The this.imageList = array line is creating a new reference. It is not updating the old reference which was passed in the Recyclerview. Hence, notifyItemChanged(position); is refreshing the view but with the old reference of the array which has not been updated.
Solution:
You need to update the method as following:
public void updatePosition(String url, int position) {
Log.e(TAG, "updatePositionCalled");
this.imageList.put(position, url);
notifyItemChanged(position);
}
And
userObject.put(PROFILE_DETAILS_CAMERA_ARRAY_KEY, cameraArray);
userObject.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
if (cameraAdapter != null) {
cameraAdapter.updatePosition(parseFile.getUrl(), position);
}
} else {
Log.e(TAG, "failed " + e.getLocalizedMessage());
}
}
});
Update:
Also, replace notifyItemChanged(position) with notifyItemInserted(position)as new item is being inserted here in the Array.
I'm trying to create a new android application that is comprised of multiple mini-games. The launcher activity extends BaseGameActivity and has a sign-in button and a ListView containing all the possible games that can be played.
Inside of a mini-game activity (also extends BaseGameActivity), how can I get it to create a notification which will launch a specific Activity? Currently, when I call invitePlayersToGame, the invitation that gets sent is for the full application (Mini-Games) and not the individual game (specific dice game).
public void invitePlayersToGame(View pView) {
Intent intent = Games.RealTimeMultiplayer.getSelectOpponentsIntent(getApiClient(), 1, 1);
intent.putExtra("gameName", "Patman Yahtzee");
startActivityForResult(intent, RC_SELECT_PLAYERS);
}
Is there a way to get the notification to generate with a specific message? Is there a way to get notification to open directly to the mini-game activity without going to the main launcher activity first?
Any help is appreciated. Thanks!
You can send sendReliableMessage for method handshaking.
First enter a room (quickgame or send invite).
public void openInvitationIntent() {
// launch the player selection screen
// minimum: 1 other player; maximum: 1 other players
Intent intent = Games.RealTimeMultiplayer.getSelectOpponentsIntent(mGoogleApiClient, 1, 1);
startActivityForResult(intent, RC_SELECT_PLAYERS);
}
onConnected:
#Override
public void onConnected(Bundle connectionHint) {
// QuickGame
if (mGameMode == 1) {
Log.d(TAG, "onConnected() called. Sign in successful!");
Log.d(TAG, "Sign-in succeeded.");
startQuickGame();
// register listener so we are notified if we receive an invitation to play
// while we are in the game
if (connectionHint != null) {
Log.d(TAG, "onConnected: connection hint provided. Checking for invite.");
Invitation inv = connectionHint.getParcelable(Multiplayer.EXTRA_INVITATION);
if (inv != null && inv.getInvitationId() != null) {
// retrieve and cache the invitation ID
Log.d(TAG, "onConnected: connection hint has a room invite!");
acceptInviteToRoom(inv.getInvitationId());
return;
}
}
}
// Send request
else if (mGameMode == 0) {
// request code for the "select players" UI
// can be any number as long as it's unique
invitationInbox();
}
// request accepted
else {
mIncomingInvitationId = getIntent().getExtras().getString(AppConstants.RC_INVITATION_ID);
RoomConfig.Builder roomConfigBuilder = makeBasicRoomConfigBuilder();
roomConfigBuilder.setInvitationIdToAccept(mIncomingInvitationId);
Games.RealTimeMultiplayer.join(mGoogleApiClient, roomConfigBuilder.build());
// prevent screen from sleeping during handshake
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
After this, you can send model class (includes what you need).
private void broadcastMessage(ModelGameRecievedMessage broadcastedMessage, boolean isFinal) {
try {
if ( mParticipants != null && broadcastedMessage != null) {
byte[] bytes = Utils.serialize(broadcastedMessage);
// Send to every other participant.
for (Participant p : mParticipants) {
if (p.getParticipantId().equals(mMyId)) {
continue;
}
if (p.getStatus() != Participant.STATUS_JOINED) {
continue;
}
if (mRoomId != null) {
if (isFinal) {
// final score notification must be sent via reliable broadcastedMessage
Games.RealTimeMultiplayer.sendReliableMessage(mGoogleApiClient, null, bytes,
mRoomId, p.getParticipantId());
} else {
// it's an interim score notification, so we can use unreliable
Games.RealTimeMultiplayer.sendUnreliableMessage(mGoogleApiClient, bytes,
mRoomId, p.getParticipantId());
}
}
}
Logy.l("broadcastedMessage.getMessageTypeId(); " + broadcastedMessage.getMessageTypeId());
Logy.l("broadcastedMessage.getMessage(); " + broadcastedMessage.getMessage());
}
} catch (IOException e) {
e.printStackTrace();
}
}
finally you can reach the data on other devices:
#Override
public void onRealTimeMessageReceived(RealTimeMessage rtm) {
byte[] bufy = rtm.getMessageData();
ModelGameRecievedMessage recievedMessage = null;
try {
recievedMessage = (ModelGameRecievedMessage) Utils.deserialize(bufy);
Logy.l("recievedMessage.getMessageTypeId(); " + recievedMessage.getMessageTypeId());
Logy.l("recievedMessage.getMessage(); " + recievedMessage.getMessage());
} catch (Exception e) {
Logy.e("Exception onRealTimeMessageReceived deserialize: " + e);
}
switch (recievedMessage.getMessageTypeId()) {
case AppConstants.RC_MULTI_START_TIMEMILIS_MULTIPLAYER:
....
I have the following Reciever and I get an app crash on device boot.
Since it happens on boot I cannot attach the debug via eclipse nor see anything in the logcat.
How would you suggest for me to see the error causing the crash?
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context arg0, Intent intent) {
// TODO Auto-generated method stub
if (intent != null) {
String action = intent.getAction();
if (action != null) {
if (action.equalsIgnoreCase(Intent.ACTION_BOOT_COMPLETED)) {
// GeoPushService geoPs = new GeoPushService();
ZoomerLocationService locService = new ZoomerLocationService();
locService.startService(new Intent());
// Log.d("receiver","action is: boot");
}
}
}
}
}
I have tried adding this try-catch
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context arg0, Intent intent) {
// TODO Auto-generated method stub
if (intent != null) {
String action = intent.getAction();
try {
if (action != null) {
if (action.equalsIgnoreCase(Intent.ACTION_BOOT_COMPLETED)) {
// GeoPushService geoPs = new GeoPushService();
ZoomerLocationService locService = new ZoomerLocationService();
locService.startService(new Intent());
// Log.d("receiver","action is: boot");
}
}
} catch (Exception ex) {
Log.e(MyLogger.TAG, ex.getStackTrace().toString());
}
}
}
}
but it didn't help
I have tried to send BOOT_COMPLETE intent and i got permissions denial
You might be able to use ADB in a command line to record the logcat when your device is booting up.
http://developer.android.com/tools/help/logcat.html
http://www.herongyang.com/Android/Debug-adb-logcat-Command-Option-Log-Buffer.html
Make sure to increase the amount of data the command window can display or else use the options to save the log to a file.
Using this method you might be able to see the crash in the log on startup.
EDIT: I have tried this and it is possible, this should work for you
I followed these instructions to integrate both Libgdx and native android code using ActionResolver interface. I have no problem calling the Android method from the Libgdx part of my code. But I am hitting a dead end when I am trying to intergrate Google IAP with Libgdx. According to TrivialDrive example, it uses mPurchaseFinishedListener (outside of calling method).
My question is: how do I pass this IAP resultcode back to Libgdx since the listener is outside the calling method? Currently, purchase process went through, but the libgdx part of my code is not being "informed" of the purchase status/result.
This is my code:
Any help is much appreciated.
ActionResolver:
public interface IActionResolver {
public int requestIabPurchase(int product);
}
MainActivity:
public class MainActivity extends AndroidApplication implements IActionResolver {
// Debug tag, for logging
static final String TAG = "greatgame";
// Does the user have the premium upgrade?
boolean mIsUpgraded = false;
// SKUs for our products: the cat, all, or pow
static final String SKU_UPGRADE = "android.test.purchased";
// (arbitrary) request code for the purchase flow
static final int RC_REQUEST = 10001;
// The helper object
IabHelper mHelper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 = false;
initialize(new Catland(this), cfg);
}
void iAbStartup() {
String base64EncodedPublicKey = "some key";
// Create the helper, passing it our context and the public key to verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(this, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
// Start setup. This is asynchronous and the specified listener
// will be called once setup completes.
Log.d(TAG, "Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log.d(TAG, "Problem setting up in-app billing: " + result);
return;
}
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
return;
}
// IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d(TAG, "Setup successful. Querying inventory.");
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
}
// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
return;
}
// Is it a failure?
if (result.isFailure()) {
Log.d(TAG, "Failed to query inventory: " + result);
return;
}
Log.d(TAG, "Query inventory was successful.");
// Do we have the SKU_UPGRADE upgrade?
Purchase thisUpgrade = inventory.getPurchase(SKU_UPGRADE);
mIsUpgraded = (thisUpgrade != null && verifyDeveloperPayload(thisUpgrade));
Log.d(TAG, "User is " + (mIsUpgraded ? "Upgraded" : "Free"));
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
runPurchaseFlow(submitProduct);
}
};
// Run real purchase flow
public void runPurchaseFlow(int product) {
Log.d(TAG, "runPurchaseFlow");
/* TODO: for security, generate your payload here for verification. See the comments on
* verifyDeveloperPayload() for more info. Since this is a SAMPLE, we just use
* an empty string, but on a production app you should carefully generate this. */
String payload = "";
if (product == 1)
mHelper.launchPurchaseFlow(this, SKU_UPGRADE, RC_REQUEST, mPurchaseFinishedListener, payload);
}
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
if (result.isFailure()) {
Log.d(TAG, "Error purchasing: " + result);
return;
}
if (!verifyDeveloperPayload(purchase)) {
Log.d(TAG, "Error purchasing. Authenticity verification failed.");
return;
}
Log.d(TAG, "Purchase successful.");
if (purchase.getSku().equals(SKU_CAT)) {
// bought the upgrade!
Log.d(TAG, "Purchase Upgrade. Congratulating user.");
mIsUpgraded = true;
// how do i pass this result to the libgdx?
}
}
};
/** Verifies the developer payload of a purchase. */
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
return true;
}
#Override
public int requestIabPurchase(int product) {
iAbStartup();
return 0; // how do i get the result from mPurchaseFinishedListener?
}
}
PurchaseScreen
result = greatgame.actionResolver.requestIabPurchase(1);
You won't be able to return the result from requestIabPurchase() - the only methods of doing so would block for a long time. The best way, in my opinion, would be to create a listener interface of your own that your LibGdx project implements, and pass that into your request interface. For example:
In your libGdx project somewhere:
interface PurchaseCallback {
public int setPurchaseResult(int result);
}
ActionResolver:
public interface IActionResolver {
public int requestIabPurchase(int product, PurchaseCallback callback);
}
In PurchaseScreen, implement PurchaseCallback:
#override
public int setPurchaseResult(int result) {
// Yay! I have a result from a purchase! Maybe you want a boolean instead of an int? I don't know. Maybe an int (for the product code) and a boolean.
}
...and pass whatever is implementing PurchaseCallback (I'm assuming your PurchaseScreen does itself):
result = greatgame.actionResolver.requestIabPurchase(1, this);
Finally, hook it all up in MainActivity:
PurchaseCallback mCallback = null;
mPurchaseFinishedListener = ... etc. etc.
.
.
.
if (mCallback != null) {
mCallback.setPurchaseResult(0);
}
.
.
.
#Override
public int requestIabPurchase(int product, PurchaseCallback callback) {
mCallback = callback; // save this for later
iAbStartup();
return 0;
}
Note that you should call PurchaseCallback.setPurchaseResult() everywhere that mPurchaseFinishedListener has return, not only at the line // how do i pass this result to the libgdx? - otherwise, you will never know if a purchase failed or is just taking a really long time.