I noticed in the branch.io documentations that branch SDK can only be configured to send deep linking data to an Activity. These methods must be called to setup branch SDK:
#Override
public void onStart()
{
super.onStart();
Branch branch = Branch.getInstance();
branch.initSession(new Branch.BranchReferralInitListener()
{
#Override
public void onInitFinished(JSONObject referringParams, BranchError error)
{
if (error == null)
{
// params are the deep linked params associated with the link that the user clicked -> was re-directed to this app
// params will be empty if no data found
// ... insert custom logic here ...
}
else
{
Log.i("MyApp", error.getMessage());
}
}
}, this.getIntent().getData(), this);
}
#Override
public void onNewIntent(Intent intent)
{
this.setIntent(intent);
}
As you see the method initSession() only accepts Activity for the third input. but I want the deeplinking data be sent to an IntentService. Am I missing something and branch provides a way to do that? Or if it doesn't, how can I provide the feature? I know that I can start an invisible activity and pass data through that to the IntentService but I've read that it makes the startup slow. Any suggestions?
We don't have anything baked into the SDK for sending parameters directly to an intent service. Capturing these parameters yourself in an activity and passing them elsewhere won't take more time than any other approach, as all approaches will require an init call, and that's where the negligible delay lives.
I read the source code of Branch and found out that there are some overloaded methods which doesn't get Activity as an input. Actually they are calling initSession with Activity set as null.
/**
* <p>Initialises a session with the Branch API.</p>
*
* #param callback A {#link BranchReferralInitListener} instance that will be called
* following successful (or unsuccessful) initialisation of the session
* with the Branch API.
* #param data A {#link Uri} variable containing the details of the source link that
* led to this initialisation action.
* #return A {#link Boolean} value that will return <i>false</i> if the supplied
* <i>data</i> parameter cannot be handled successfully - i.e. is not of a
* valid URI format.
*/
public boolean initSession(BranchReferralInitListener callback, #NonNull Uri data) {
return initSession(callback, data, null);
}
I used the method declared above and defined all the needed intent filters inside my IntentService declaration in manifest, instead of an activity. I tested it and it worked. It would be nice if they had documented that.
UPDATE
It's just a misuse. Not a reliable and supported approach. It would be better to create an invisible Activity and send data through that to the IntentService.
In my android app I want to change the input method. So I start a new Activity which shows the language settings in the device. Then user can change it. However then I want to know that if the user has changed it. So I wrote a function for that also. My code so far is...
Intent enableIME = new Intent(android.provider.Settings.ACTION_INPUT_METHOD_SETTINGS);
startActivityForResult(enableIME,0);
if(isInputMethodEnabled()){
activateshadow.setBackgroundDrawable(getResources().getDrawable(R.drawable.button_pressed));
activateshadow.setText("Deactivate Shadow");
prefs.edit().putBoolean("Activate", false).commit();
}else{
Toast.makeText(MainActivity.this,"You haven't change the input method to simpleIME.In order to activate you must change it.",Toast.LENGTH_SHORT).show();
}
my is inputMethodEnabled function is....
public boolean isInputMethodEnabled() {
boolean isIME ;
String id = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
String [] name = id.split("/.");
// Toast.makeText(MainActivity.this,"s:"+name[1]+":s",Toast.LENGTH_SHORT).show();
if(name[1].contains("SimpleIME") ){
isIME = true ;
}else{
isIME = false;
}
// Toast.makeText(MainActivity.this,"Returning..."+isIME,Toast.LENGTH_SHORT).show();
return isIME;
}
if(isInputMethodEnabled()) always fails because when the new intent(settings) opens and it take some time to change the input method to simpleIME . How to fix this problem?
You catch when a launched Activity returns in onActivityResult. The requestCode you supplied to startActivityForResult will be a parameter, as will the Activity's result. The Activity may also set other data which you didn't ask about.
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 555) {//555 is the intent ID you gave in startActivityForResult(enableIME,555);
if (resultCode == /*Result1*/)
//Do something
else {
//Do something else
}
}
}
You need a unique id when calling startActivityForResult(enableIME,0);
startActivityForResult(enableIME, 555);
Better still replace 555 with a named variable.
if u look at android life cycle, when activity is finished whole android call onDestroy() method.
so u can call and override this method.
just need write:
#override
protected void onDestroy(){
// code
super.onDestroy();
}
u can manage and override all of life cycle's parts in android
e.g: onResume to get current activity
i hope this help u
I'm developing an android app with the ability to attach images from the camera.
The attach button found in two different activities, and the request code that returns from the camera when finishing is different from one activity to another, here is my code:
File f = new File(Environment.getExternalStorageDirectory()+"/aaa/temp", "temp.jpg");
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
startActivityForResult(intent, 10);
onActivityResult the returned requestCode is NOT 10 but "65546" and from the other activity the requestCode is "327690" !!!
Why does the camera activity discards the "10" that I have sent when starting it??
Is it bug? Is there any workaround?
I've had the same problem. Create a public static final int in each Activity and use the correct one as requestCode. Also, if you are calling startActivityForResult(...) from a Fragment, try changing it with 'getActivity().startActivityForResult(...)'
You need to manually set your request and results codes from both Activities (requesting and responding) which means you need to have your own Camera-activity implementation class. You should post the code from that (Camera-Activity) class here for us to help you.
A good way to be safe with correct request/result codes as stated in other answers, is to declare public static int codeX for each request/result code and use these variables in every call.
To assist you further, here is an example on how to implement concurrent request and result codes for the two communicating activities:
Requesting Activity
int requestCode=1;
int resultOk=0;
Intent intent = new Intent(getApplicationContext(), Responding.class);
startActivityForResult(intent,requestCode);
Responding Camera-Activity
int resultOk=0;
Intent returnIntent = new Intent();
// you can use this to pass your stuff to the Requesting activity
returnIntent.putExtra("extraStuff",stuff);
setResult(resultOk,returnIntent);
finish();
Requesting Activity
int resultOk=0;
#Override
protected void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if (result == resultok && request == requestCode) {
//get your extra stuff from intent
int result = data.getIntExtra("extraStuff", 0);
} else {
// Handle different scenarios
}
}
onActivityResult the returned requestCode is NOT 10 but "65546" and from the other activity the requestCode is "327690" !!!
Why does the camera activity discards the "10" that I have sent when starting it??
Is it bug? Is there any workaround?
You're starting the activity from a fragment and receiving the result in the activity. That's the problem.
You need to start the activity and get the result either both in the fragment, or both in the activity. Do not mix the two.
What happens is that in the v4 support library fragments, fragment index is encoded in the top 16 bits of the request code and your request code is in the bottom 16 bits. For example, 65546 is really 1 << 16 + 10 and 327690 is 5 << 16 + 10. This fragment index is later used to find the correct fragment to deliver the result to.
I am using the IabHelper utility classes, as recommended by Google's tutorial, and I'm being hit hard by this error. Apparently IabHelper can not run multiple async operations at the same time. I even managed to hit it by trying to start a purchase while the inventory taking was still in progress.
I have already tried to implement onActivityResult in my main class as suggested here, but I don't even get a call to that method before the error hits. Then I found this but I have no idea where to find this flagEndAsync method - it's not in the IabHelper class.
Now I'm looking for a way around this (without reimplementing the whole she-bang). The only solution I can think of is to create a boolean field asyncActive that is checked before an async task is started, and not do it if there is another task active. But that has many other problems, and doesn't work across activities. Also I'd prefer to have an async task queue up and run as soon as it's allowed to, instead of not running at all.
Any solutions for this issue?
A simple tricky solution
before calling purchaseItem method just add this line
if (billingHelper != null) billingHelper.flagEndAsync();
so your code looks this way
if (billingHelper != null) billingHelper.flagEndAsync();
purchaseItem("android.test.purchased");
Note: don't forget to make public flagEndAsync() method in IabHelper if you call it from another package.
Make sure that you call the IabHelper's handleActivityResult in the Activity's onActivityResult, and NOT in the Fragment's onActivityResult.
The following code snippet is from TrivialDrive's MainActivity:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
if (mHelper == null) return;
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}
else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
Update:
There is now a In-app Billing Version 3 API (what was the version in 2013?)
The code sample has moved to Github. Snippet above edited to reflect current sample, but is logically the same as before.
This was not easy to crack but I found the needed workarounds. Quite disappointed with Google lately, their Android web sites have become a mess (very hard to find useful info) and their sample code is poor. When I was doing some Android development a few years ago it all went so much easier! This is yet another example of that...
Indeed IabUtil is buggy, it does not properly call off its own async tasks. The complete set of necessary workarounds to stabilise this thing:
1) make method flagEndAsync public. It is there, just not visible.
2) have every listener call iabHelper.flagEndAsync to make sure the procedure is marked finished properly; it seems to be needed in all listeners.
3) surround calls with a try/catch to catch the IllegalStateException which may occur, and handle it that way.
I ended up doing something similar to Kintaro. But added mHelper.flagEndAsync() to the end of the catch. The user still gets the toast but by the next time they push the purchase button, the async operation has been killed and the purchase button is ready to go again.
if (mHelper != null) {
try {
mHelper.launchPurchaseFlow(this, item, RC_REQUEST, mPurchaseFinishedListener, "");
}
catch(IllegalStateException ex){
Toast.makeText(this, "Please retry in a few seconds.", Toast.LENGTH_SHORT).show();
mHelper.flagEndAsync();
}
}
Find flagEndAsync() inside IabHelper.java file and change it to a public function.
Before trying purchase call flagEndAsync() for your IabHelper
You must do somthig like this code :
mHelper.flagEndAsync();
mHelper.launchPurchaseFlow(AboutActivity.this, SKU_PREMIUM, RC_REQUEST, mPurchaseFinishedListener, "payload-string");
I was having the same issue until I stumbled upon another SO thread. I'm including a touched up version of the code found in the other thread that you need to include in your Activity that initialises the purchase.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Pass on the activity result to the helper for handling
// NOTE: handleActivityResult() will update the state of the helper,
// allowing you to make further calls without having it exception on you
if (billingHelper.handleActivityResult(requestCode, resultCode, data)) {
Log.d(TAG, "onActivityResult handled by IABUtil.");
handlePurchaseResult(requestCode, resultCode, data);
return;
}
// What you would normally do
// ...
}
A simple trick that did it for me was to create a method in IabHelper:
public Boolean getAsyncInProgress() {
return mAsyncInProgress;
}
and then in your code, just check:
if (!mHelper.getAsyncInProgress())
//launch purchase
else
Log.d(TAG, "Async in progress already..)
Really annoying issue. Here is a quick and dirty solution that is not perfect code wise, but that is user friendly and avoids bad ratings and crashes:
if (mHelper != null) {
try {
mHelper.launchPurchaseFlow(this, item, RC_REQUEST, mPurchaseFinishedListener, "");
}
catch(IllegalStateException ex){
Toast.makeText(this, "Please retry in a few seconds.", Toast.LENGTH_SHORT).show();
}
}
This way the user just has to tap another time (2 times at worst) and gets the billing popup
Hope it helps
Just check for the onActivityResult requestCode on the activity and if it matches the PURCHASE_REQUEST_CODE you used on the purchase just pass it to the fragment.
When you add or replace the fragment in the FragmentTransaction just set a tag:
fTransaction.replace(R.id.content_fragment, fragment, fragment.getClass().getName());
Then on your activity's onActivityResult
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == PurchaseFragment.PURCHASE_REQUEST_CODE) {
PurchaseFragment fragment = getSuportFragmentManager().findFragmentByTag(PurchaseFragment.class.getNAme());
if(fragment != null) {
fragment.onActivityResult(requestCode, resultCode, data);
}
}
}
if you code in fragment then you this code in IabHelper.java
void flagStartAsync(String operation) {
if (mAsyncInProgress) {
flagEndAsync();
}
if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
mAsyncOperation = operation;
mAsyncInProgress = true;
logDebug("Starting async operation: " + operation);
}
Or, you can get the latest IabHelper.java file here: https://code.google.com/p/marketbilling/source/browse/
The March 15th version fixed this for me. (Note other files with no changes were committed on the 15th)
I still had to fix one crash that happened during testing caused by a null intent extras when "android.test.canceled" was the sent SKU. I changed:
int getResponseCodeFromIntent(Intent i) {
Object o = i.getExtras().get(RESPONSE_CODE);
to:
int getResponseCodeFromIntent(Intent i) {
Object o = i.getExtras() != null ? i.getExtras().get(RESPONSE_CODE) : null;
I have had this issue occasionally, and in my case I've tracked it down to the fact that if the onServiceConnected method in IabHelper can be called more than once if the underlying service disconnects and reconnects (e.g. due to an intermittent network connection).
The specific operations in my case were "Can't start async operation (refresh inventory) because another async operation(launchPurchaseFlow) is in progress."
The way that my app is written, I can't call launchPurchaseFlow until after I've had a completed queryInventory, and I only call queryInventory from my onIabSetupFinished handler function.
The IabHelper code will call this handler function whenever its onServiceConnected is called, which can happen more than once.
The Android documentation for onServiceDisconnected says:
Called when a connection to the Service has been lost. This typically happens when the
process hosting the service has crashed or been killed. This does not remove the
ServiceConnection itself -- this binding to the service will remain active, and you will
receive a call to onServiceConnected(ComponentName, IBinder) when the Service is next
running.
which explains the problem.
Arguably, IabHelper shouldn't call the onIabSetupFinished listener function more than once, but on the other hand it was trivial to fix the problem in my app by simply not calling queryInventory from within this function if I've already done it and got the results.
Another major issue with the IabHelpr class is the poor choice of throwing RuntimeExcptions (IllegalStateException) in multiple methods. Throwing RuntimeExeptions from your own code in most cases is not desirable due to the fact that they are unchecked exceptions. That is like sabotaging your own application- if not caught, these exceptions will bubble up and crash your app.
The solution to this is to implement your own checked exception and change the IabHelper class to throw it, instead of the IllegalStateException. That will force you to handle this exception everywhere it could be thrown in your code at compile time.
Here is my custom exception:
public class MyIllegalStateException extends Exception {
private static final long serialVersionUID = 1L;
//Parameterless Constructor
public MyIllegalStateException() {}
//Constructor that accepts a message
public MyIllegalStateException(String message)
{
super(message);
}
}
Once we make the changes in the IabHelper class, we can handle our checked exception in our code where we call the class methods. For example:
try {
setUpBilling(targetActivityInstance.allData.getAll());
} catch (MyIllegalStateException ex) {
ex.printStackTrace();
}
I had the same issue and the problem was that I didn't implement the method onActivityResult.
#Override
protected void onActivityResult(int requestCode,
int resultCode,
Intent data)
{
try
{
if (billingHelper == null)
{
return;
} else if (!billingHelper.handleActivityResult(requestCode, resultCode, data))
{
super.onActivityResult(requestCode, resultCode, data);
}
} catch (Exception exception)
{
super.onActivityResult(requestCode, resultCode, data);
}
}
Yes, i am also facing this issue but i resolved this but i resolved using
IabHelper mHelpermHelper = new IabHelper(inappActivity, base64EncodedPublicKey);
mHelper.flagEndAsync();
The above method stop all the flags. Its work for me must check
This answer directly addresses the problem that #Wouter has seen...
It is true that onActivityResult() must be triggered, like many people have said. However, the bug is that Google's code isn't triggering onActivityResult() in certain circumstances, i.e. when you're pressing your [BUY] button twice when running the debug build of your app.
Additionally, one major problem is that the user may be in a shaky environment (i.e. Bus or subway) and presses your [BUY] button twice... suddenly you've got yourself an exception !
At least Google fixed this embarrassing exception https://github.com/googlesamples/android-play-billing/commit/07b085b32a62c7981e5f3581fd743e30b9adb4ed#diff-b43848e47f8a93bca77e5ce95b1c2d66
Below is what I implemented in the same class where IabHelper is instantiated (for me, this is in the Application class) :
/**
* invokes the startIntentSenderForResult - which will call your activity's onActivityResult() when it's finished
* NOTE: you need to override onActivityResult() in your activity.
* NOTE2: check IAB code updates at https://github.com/googlesamples/android-play-billing/tree/master/TrivialDrive/app/src/main/java/com/example/android/trivialdrivesample/util
* #param activity
* #param sku
*/
protected boolean launchPurchaseWorkflow(Activity activity, String sku)
{
if (mIabIsInitialized)
{
try
{
mHelper.launchPurchaseFlow(
activity,
sku,
Constants.PURCHASE_REQUEST_ID++,// just needs to be a positive number and unique
mPurchaseFinishedListener,
Constants.DEVELOPER_PAYLOAD);
return true;//success
}
catch (IllegalStateException e)
{
mHelper.flagEndAsync();
return launchPurchaseWorkflow(activity, sku);//recursive call
}
}
else
{
return false;//failure - not initialized
}
}
My [BUY] button calls this launchPurchaseWorkflow() and passes the SKU and the activity the button is in (or if you're in a fragment, the enclosing activity)
NOTE: be sure to make IabHelper.flagEndAsync() public.
Hopefully, Google will improve this code in the near future; this problem is about 3 years old and it's still an ongoing problem :(
My solution is simple
1.) Make the mAsyncInProgress variable visible outside of IabHelper
public boolean isAsyncInProgress() {
return mAsyncInProgress;
}
2.) Use this in your Activity like:
...
if (mIabHelper.AsyncInProgress()) return;
mIabHelper.queryInventoryAsync(...);
...
A little-modified version of NadtheVlad's answer that works like charm
private void makePurchase() {
if (mHelper != null) {
try {
mHelper.launchPurchaseFlow(getActivity(), ITEM_SKU, 10001, mPurchaseFinishedListener, "");
}
catch(IllegalStateException ex){
mHelper.flagEndAsync();
makePurchase();
}
}
}
The logic is simple, just put the launchPurchaseFlow() thing in a method and use recursion in the catch block. You still need to make flagEndAsync() public from the IabHelper class.
I have same issue, but it resolved!
I think you must be not run "launchPurchaseFlow" on UI thread, try to run launchPurchaseFlow on UI thread,
it would be working fine!
mActivity.runOnUiThread(new Runnable(){
public void run(){
mHelper.launchPurchaseFlow(mActivity, item, 10001, mPurchaseFinishedListener,username);
}
});
I am trying to do programmatic update to the application I am writing, since it is not a Google Play application and I want to provide a way to do updates.
I've been searching around and found out how to start the Android installer after I download the APK for the update, but I need to get a result from the installer, that tells me if the update succeeded or not, or if it was cancelled by the user.
I saw a bunch of questions on StackOverflow about this, and the answers usually involved using a broadcast receiver. The problem with that is that it can only receive intents about the package being installed, not about canceled installs of fails.
I did some more research and it seems the Intent API provides some extras such as Intent.EXTRA_RETURN_RESULT, which if set to true should return a result from the installer activity - I guess via onActivityResult. Unfortunately this doesn't work. Is there anybody that got this working/does it work like this?
Here is the code preparing the installer activity start, that I currently have:
Intent installApp = new Intent(Intent.ACTION_INSTALL_PACKAGE);
installApp.setData(downloadedApk);
installApp.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
installApp.putExtra(Intent.EXTRA_RETURN_RESULT, true);
installApp.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.getApplicationInfo().packageName);
context.startActivityForResult(installApp, 1);
Do you use Fragments? The onActivityResult will be called from the Activity or Fragment you have called startActivity(...). Fragment#startActivity(...) does exist. Use it to get the Fragment's onActivityResult(...) called.
If you are not using Fragments, this Workaround will work.
Workaround Pseudocode
// CURRENT_VERSION is a const with the current APK version as int
Activity#onStart() {
super.onStart();
checkForUpdaterResult();
/*...*/
}
Activity#checkForUpdaterResult() {
final int updateVersion = preferences.getInt(UPDATE_VERSION, -1);
switch(updateVersion) {
case -1:break;
default:
// updateVersion = oldVersion is smaller than the new currentVersion
boolean success = updateVersion < CURRENT_VERSION;
onUpdaterPerformed(success, updateVersion , CURRENT_VERSION);
break;
}
}
Activity#startUpdate(File pAPK) {
perferences.putInt(UPDATE_VERSION, CURRENT_VERSION);
/*...*/
}
Activity#onUpdaterPerformed(boolean pSuccess, int pFromVersion, int pToVersion) {
Toast.show("Update success: " + pSuccess);
/* e.g. migrate DB */
/*...*/
}