I'm querying subscription status with following code. With this I'm able to get boolean status about this subscription. Will this result affected by network status or package uninstall/reinstall or any other criteria. Is the right to way to find the subscription status ?
mHelper = new IabHelper(this, PUBLIC_KEY);
mHelper.startSetup( new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if ( ! result.isSuccess()) {
return;
}
if (QueryInventoryListner.mHelper == null){
return;
}
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
& Query Inventory Finish Listner
mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
#Override
public void onQueryInventoryFinished(final IabResult result, final Inventory inventory) {
Purchase subscriptionForFullVersion = inventory.getPurchase(SKU_SUBSCRIPTION);
boolean isSubscribe = subscriptionForFullVersion != null ;
if( isSubscribe ) {
//User is subscribed to SKU_SUBSCRIPTION
}
}
This example of code works even after reinstalling an application.
You forget to add try\catch block:
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
return;
}
if (QueryInventoryListner.mHelper == null){
return;
}
try {
mHelper.queryInventoryAsync(mGotInventoryListener);
} catch (IabAsyncInProgressException e) {
complain(context.getResources().getString(R.string.subscription_error_subscription_error_to_query_inventory_another_async));
}
}
and my mGotInventoryListener like this:
// Listener that's called when we finish querying the items and subscriptions we own
private QueryInventoryFinishedListener mGotInventoryListener = new QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Logging.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()) {
complain(context.getResources().getString(R.string.subscription_error_subscription_error_to_query_inventory) + " " + result);
return;
}
Logging.d(TAG, "Query inventory was successful.");
// First find out which subscription is auto renewing
Purchase subscriptionMonthly = inventory.getPurchase(SKU_SUBSRIPTION_MONTHLY);
// The user is subscribed if either subscription exists, even if neither is auto
// renewing
mSubscribedToFreeAds = (subscriptionMonthly != null);
Logging.d(TAG, "User " + (mSubscribedToFreeAds ? "HAS" : "DOES NOT HAVE")
+ " monthly subscription.");
if (mSubscribedToFreeAds) {
putPurchase(subscriptionMonthly);//save purchase
isSubscribed = true;
} else {
clearPurchase();
isSubscribed = false;
}
updateUi();
setWaitScreen(false);
Logging.d(TAG, "Initial inventory query finished; enabling main UI.");
}
};
Also you can test your subscription before release:
Setting Up for Test Purchases
Related
I'm using agora to make voice calls in my android application , i have set up the code as per the documentation but whenever i try to join a call it crashes and says that rtcEngine is null even though it is initialized , any help would be appreciated , Thank you
Code
public class AudioCallActivity extends AppCompatActivity {
// An integer that identifies the local user.
private int uid = 0;
// Track the status of your connection
private boolean isJoined = false;
// Agora engine instance
private RtcEngine agoraEngine;
// UI elements
private TextView infoText;
private Button joinLeaveButton;
private static final int PERMISSION_REQ_ID = 22;
private static final String[] REQUESTED_PERMISSIONS = { Manifest.permission.RECORD_AUDIO};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_call);
// If all the permissions are granted, initialize the RtcEngine object and join a channel.
if (!checkSelfPermission()) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, PERMISSION_REQ_ID);
}
setupVoiceSDKEngine();
// Set up access to the UI elements
joinLeaveButton = findViewById(R.id.joinLeaveButton);
infoText = findViewById(R.id.infoText);
}
private boolean checkSelfPermission() {
return ContextCompat.checkSelfPermission(this, REQUESTED_PERMISSIONS[0]) == PackageManager.PERMISSION_GRANTED;
}
void showMessage(String message) {
runOnUiThread(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show());
}
private void setupVoiceSDKEngine() {
try {
RtcEngineConfig config = new RtcEngineConfig();
config.mContext = getBaseContext();
config.mAppId = getString(R.string.app_id);
config.mEventHandler = mRtcEventHandler;
agoraEngine = RtcEngine.create(config);
} catch (Exception e) {
throw new RuntimeException("Check the error.");
}
}
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
#Override
// Listen for the remote user joining the channel.
public void onUserJoined(int uid, int elapsed) {
runOnUiThread(()->infoText.setText("Remote user joined: " + uid));
}
#Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
// Successfully joined a channel
isJoined = true;
showMessage("Joined Channel " + channel);
runOnUiThread(()->infoText.setText("Waiting for a remote user to join"));
}
#Override
public void onUserOffline(int uid, int reason) {
// Listen for remote users leaving the channel
showMessage("Remote user offline " + uid + " " + reason);
if (isJoined) runOnUiThread(()->infoText.setText("Waiting for a remote user to join"));
}
#Override
public void onLeaveChannel(RtcStats stats) {
// Listen for the local user leaving the channel
runOnUiThread(()->infoText.setText("Press the button to join a channel"));
isJoined = false;
}
};
private void joinChannel() {
ChannelMediaOptions options = new ChannelMediaOptions();
options.autoSubscribeAudio = true;
// Set both clients as the BROADCASTER.
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// Set the channel profile as BROADCASTING.
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// Join the channel with a temp token.
// You need to specify the user ID yourself, and ensure that it is unique in the channel.
agoraEngine.joinChannel(getString(R.string.agora_token), "ChannelOne", uid, options);
}
public void joinLeaveChannel(View view) {
try {
if (isJoined) {
agoraEngine.leaveChannel();
joinLeaveButton.setText("Join");
} else {
joinChannel();
joinLeaveButton.setText("Leave");
}
}catch (Exception e){
Log.d("TAG","Error is " + e.getMessage());
}
}
#Override
protected void onDestroy() {
agoraEngine.leaveChannel();
// Destroy the engine in a sub-thread to avoid congestion
new Thread(() -> {
RtcEngine.destroy();
agoraEngine = null;
}).start();
onDestroy();
}
}
Error Thrown
Attempt to invoke virtual method 'int io.agora.rtc2.RtcEngine.joinChannel(java.lang.String, java.lang.String, int, io.agora.rtc2.ChannelMediaOptions)' on a null object reference
I want to store the previous monthly event data in a variable and display in case of unsuccessful response call , as of now it is displaying empty list, please suggest me how we can do that
//this loads events for month view
public void loadEvents(int year, int month) {
RetrofitEvent.getEventApi().getEvents(year, month, userName).enqueue(new Callback<List<Event>>() {
#Override
public void onResponse(Call<List<Event>> call, Response<List<Event>> response) {
if ((!response.isSuccessful()) || response.body() == null) {
eventsLiveData.setValue(Collections.emptyList());
return;
}
Log.d("MyFitness229789", "posting value: " + response.body().size() + " <- size");
new Thread() {
#Override
public void run() {
Collections.sort(response.body(), getComparator());
eventsLiveData.postValue(response.body());
Log.d("MyFitness229789", "posting value: " + response.body().size());
for (Event event :
response.body()) {
Log.d("MyFitness229789", "run: " + event.toString());
}
}
}.start();
}
#Override
public void onFailure(Call<List<Event>> call, Throwable t) {
eventsLiveData.setValue(Collections.emptyList());
}
});
}
I'm in the process of implementing in app billing for Android and have got to the point where I can retrieve a list of products from the store. And can activate the Google purchase dialog via calling the launchBillingFlow() method. The documentation indicates that once this has been called, the onPurchasesUpdated is then called with the result. However this isn't happening for me.
The logging confirms that the purchase is requested (from within my method: startPurchaseFlow()). My onPurchasesUpdated() is also called when the activity first runs and provides a OK result (0) to confirm connection set up.
But why isn't it being called after launchBillingFlow()?
Class that holds purchase mechanics:
public class BillingManager implements PurchasesUpdatedListener {
private final BillingClient mBillingClient; // Billing client used to interface with Google Play
private final Store mActivity; // Referenced in constructor
// Structure to hold the details of SKUs returned from querying store
private static final HashMap<String, List<String>> SKUS;
static
{
SKUS = new HashMap<>();
SKUS.put(BillingClient.SkuType.INAPP, Arrays.asList("com.identifier.unlock")); // Strings for in app permanent products
}
public List<String> getSkus(#BillingClient.SkuType String type) {
return SKUS.get(type);
}
// Constructor
public BillingManager(Store activity) {
mActivity = activity;
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build(); // Initialise billing client and set listener
mBillingClient.startConnection(new BillingClientStateListener() { // Start connection via billing client
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponse) { // Actions to complete when connection is set up
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i("dev", "onBillingSetupFinished() response: " + billingResponse);
mActivity.getProducts();
} else {
Log.w("dev", "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() { // Called when the connection is disconnected
Log.w("dev", "onBillingServiceDisconnected()");
}
});
}
// Receives callbacks on updates regarding future purchases
#Override
public void onPurchasesUpdated(#BillingClient.BillingResponse int responseCode,
List<Purchase> purchases) {
Log.d(TAG, "onPurchasesUpdated() response: " + responseCode);
if (responseCode == 0 && !purchases.isEmpty()) {
String purchaseToken;
for (Purchase element : purchases) {
purchaseToken = element.getPurchaseToken();
mBillingClient.consumeAsync(purchaseToken, null); // Test to 'undo' the purchase TEST
}
}
}
// Used to query store and get details of products args include products to query including type and list of SKUs and a listener for response
public void querySkuDetailsAsync(#BillingClient.SkuType final String itemType,
final List<String> skuList, final SkuDetailsResponseListener listener) {
// Create a SkuDetailsParams instance containing args
SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
.setSkusList(skuList).setType(itemType).build();
//Query the billing client using the SkuDetailsParams object as an arg
mBillingClient.querySkuDetailsAsync(skuDetailsParams,
new SkuDetailsResponseListener() {
// Override the response to use the listener provided originally in args
#Override
public void onSkuDetailsResponse(int responseCode,
List<SkuDetails> skuDetailsList) {
listener.onSkuDetailsResponse(responseCode, skuDetailsList);
}
});
}
// Start purchase flow with retry option
public void startPurchaseFlow(final String skuId, final String billingType) {
Log.i("dev", "Starting purchaseflow...");
// Specify a runnable to start when connection to Billing client is established
Runnable executeOnConnectedService = new Runnable() {
#Override
public void run() {
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setType(billingType)
.setSku(skuId)
.build();
mBillingClient.launchBillingFlow(mActivity, billingFlowParams);
Log.i("dev", "Just called launchBillingFlow..." + skuId);
}
};
// If Billing client was disconnected, we retry 1 time
// and if success, execute the query
startServiceConnectionIfNeeded(executeOnConnectedService);
}
// Starts connection with reconnect try
private void startServiceConnectionIfNeeded(final Runnable executeOnSuccess) {
if (mBillingClient.isReady()) {
if (executeOnSuccess != null) {
executeOnSuccess.run();
}
} else {
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponse) {
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
if (executeOnSuccess != null) {
executeOnSuccess.run();
}
} else {
Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "onBillingServiceDisconnected()");
}
});
}
}
} // End of class
Class that implements interface and initiates request for purchases and displays product information:
public class Store extends AppCompatActivity {
SharedPreferences prefs; // used to access and update the pro value
BillingManager billingManager; // Used to process purchases
// Following are used to store local details about unlock product from the play store
String productSku = "Loading"; // Holds SKU details
String productBillingType = "Loading";
String productTitle = "Loading"; // Will be used to display product title in the store activity
String productPrice = "Loading"; // Used to display product price
String productDescription = "Loading"; // Used to display the product description
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_store);
// Set up toolbar
Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
setSupportActionBar(myToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
// Create billing manager instance
billingManager = new BillingManager(this);
// Set up the shared preferences variable
prefs = this.getSharedPreferences(
"com.identifier", Context.MODE_PRIVATE); // Initiate the preferences
// set up buttons
final Button btnBuy = findViewById(R.id.btnBuy);
btnBuy.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
billingManager.startPurchaseFlow(/*productSku*/ "android.test.purchased", productBillingType); // Amended for TEST
}
});
final Button btnPro = findViewById(R.id.btnPro);
btnPro.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
getProducts();
}
});
getProducts();
updateDisplay();
} // End of onCreate
// Used to unlock the app
public void unlock() {
Log.d("dev", "in unlock(), about to set to true");
prefs.edit().putBoolean("pro", true).apply();
MainActivity.pro = true;
}
// Go back if back/home pressed
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
// Used to request details of products from the store from this class
public void getProducts() {
List<String> inAppSkus = billingManager.getSkus(BillingClient.SkuType.INAPP); // Create local list of Skus for query
billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, inAppSkus, new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) {
for (SkuDetails details : skuDetailsList) {
productSku = details.getSku();
productTitle = details.getTitle();
productDescription = details.getDescription();
productPrice = details.getPrice();
productBillingType = details.getType();
}
updateDisplay();
}
}
});
}
// Helper method to update the display with strings
private void updateDisplay() {
final TextView titleText = findViewById(R.id.txtTitle);
final TextView descriptionText = findViewById(R.id.txtDescription);
final TextView priceText = findViewById(R.id.txtPrice);
titleText.setText(productTitle);
descriptionText.setText(productDescription);
priceText.setText(productPrice);
}
}
Ok, so this (replacing the onPurchasesUpdated method above) is now working/responding as expected. Why, I don't know, but it is.
#Override
public void onPurchasesUpdated(#BillingClient.BillingResponse int responseCode,
List<Purchase> purchases) {
if (responseCode == BillingClient.BillingResponse.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
Log.d(TAG, "onPurchasesUpdated() response: " + responseCode);
Log.i("dev", "successful purchase...");
String purchasedSku = purchase.getSku();
Log.i("dev", "Purchased SKU: " + purchasedSku);
String purchaseToken = purchase.getPurchaseToken();
mBillingClient.consumeAsync(purchaseToken, null); // Test to 'undo' the purchase TEST
mActivity.unlock();
}
} else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
Log.d(TAG, "onPurchasesUpdated() response: User cancelled" + responseCode);
} else {
// Handle any other error codes.
}
}
I have a spinner and an onItemSelected that compares a ArrayList to Parse.com, and then runs a Scan method that runs a bluetooth scan and if it finds a certain mac adress against parse it will make a toast.
I want to make that toast custom, but when the custom arraylist is == null, it should spit out a standard toast. This works fine when I DONT check for the list == null. If I enter something for the toast it runs fine and displays the custom toast. So the toast must never be null eventho nothing is defined. I have tried customnotifications.clear several places in the code before something is added, but it does not work.
This is my statement for the check;
public void ScanBL() {
final ParseQuery<ParseUser> queryParseUser = ParseUser.getQuery();
queryParseUser.findInBackground(new FindCallback<ParseUser>() {
#Override
public void done(List<ParseUser> BTList, ParseException arg1) {
// TODO Auto-generated method stub
//Log.d("Parse", "we made it this far");
btadapter.cancelDiscovery();
btadapter.startDiscovery();
if (BTList != null && arg1 == null && list != null) {
for (ParseUser parseObject : BTList) {
if (parseObject.getString("BT_ID") != null) {
for (String string : list) {
for(String s : customnotification) {
Log.d("Custom", s);
}
if (string.equals(parseObject.getString("BT_ID")) && excluded.contains(parseObject.getString("BT_ID")) && customnotification == null) {
String user = parseObject.getString("username");
Log.d("For each", user);
Toast toast2 = Toast.makeText(context, "Wow, " + user + " is nearby! Test", Toast.LENGTH_SHORT);
toast2.show();
}
else if (string.equals(parseObject.getString("BT_ID")) && excluded.contains(parseObject.getString("BT_ID"))){
String user = parseObject.getString("username");
for (String string2 : customnotification) {
Toast toast = Toast.makeText(context, user + string2, Toast.LENGTH_SHORT);
toast.show();
}
//out.append("\nFound nearby device: " + user);
//Log.d("Bluetooth", "burde parse?");
}
}
}
}
}
else {
Log.d("Bluetooth", "Fejl i returnering af data: ");
}
}
});
}
And here I add the customnotifications arraylist;
protected void changeNotifications() {
customnotification.clear();
String changemessage = changeMessage.getText().toString();
Toast.makeText(getApplicationContext(), "Notifikation changed to " + changemessage, Toast.LENGTH_LONG).show();
customnotification.add(changemessage);
for (String string : customnotification){
System.out.println(string);
}
}
How on earth do I make it check correctly? Been at this for a day :(
It's been pointed out to me by a friend that ArrayLists cannot be null, so the obvious little change was simple
if (string.equals(parseObject.getString("BT_ID")) && excluded.contains(parseObject.getString("BT_ID")) && customnotification == null)
changed to
if (string.equals(parseObject.getString("BT_ID")) && excluded.contains(parseObject.getString("BT_ID")) && customnotification.isEmpty())
I have this code below that is in my main activity. Basicly whenever I click the button, it will first check if an alarm is set, if it is false it will go into a loop which reads the RSSI on a connected device until it is above a RSSI value. My question is how do i make this loop not crash my app, which it currently does. Also for some reason the mRSSI text field never gets populated with the RSSI value. Can someone please help me out. This is the last thing in my app i need to get done.
public void onMonitorClick(final View view){
if (isBLEEnabled()) {
if (!isDeviceConnected()) {
// do nothing
} else if (isImmediateAlertOn == true) {
showMonitor();
DebugLogger.v(TAG, "app is high alert");
isImmediateAlertOn = true;
}
else {
DebugLogger.v(TAG, "app is no alert");
hideMonitor();
while(monitorStop != 1)
{
((ProximityService.ProximityBinder) getService()).getRssi();
rssilevel = ((ProximityService.ProximityBinder) getService()).getRssiValue();
if (rssilevel > -50 ) {
DebugLogger.v(TAG, "greater then -50");
monitorStop = 1;
}
mRSSI.setText("-" + String.valueOf(rssilevel) + "dB");
isImmediateAlertOn = false;
mFindMeButton.setEnabled(false);
}
}
} else {
showBLEDialog();
}
}
edit redone code
public void onMonitorClick(final View view){
if (isBLEEnabled()) {
if (!isDeviceConnected()) {
// do nothing
} else if (monitorvis == 0) {
showMonitor();
} else if (isImmediateAlertOn == true) {
showMonitor();
DebugLogger.v(TAG, "app is high alert");
isImmediateAlertOn = true;
}
else {
DebugLogger.v(TAG, "app is no alert");
hideMonitor();
monitorStop = 0;
do { run(); run2(); } while(monitorStop != 1);
}
} else {
showBLEDialog();
}
}
protected void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
((ProximityService.ProximityBinder) getService()).getRssi();
rssilevel = ((ProximityService.ProximityBinder) getService()).getRssiValue();
mRSSI.setText("-" + String.valueOf(rssilevel) + "dB");
}
});
}
protected void run2() {
runOnUiThread(new Runnable() {
#Override
public void run() {
mRSSI.setText("-" + String.valueOf(rssilevel) + "dB");
if (rssilevel < -60)
{
monitorStop = 1;
showMonitor();
((ProximityService.ProximityBinder) getService()).startImmediateAlert();
}
}
});
}
This is (IMHO) the easiest way to delay execution of a piece of code:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// Do what you need to do
}
}, MILISECONDS_BEFORE_EXECUTION);
Here, MILISECONDS_BEFORE_EXECUTION is a value (constant or variable) of the milliseconds you need to wait before executing the code. Documentation of Handler in Android.