the problem: if user doesn't upload any image, app crashes, as cannot store previous image...
Overview: my application consists of Category and Editor activities.
Editor activity has a button to upload image and save the activity.
Category activity displays image from Editor activity.
Problem here --> once the user returns back to edit details in Editor activity, app crashes with below error:
Attempt to invoke virtual method 'java.lang.String android.net.Uri.toString()' on a null object reference
If user uploads an image again - same or different, doesn't matter, the app works well.
The goal is to keep the image if the user doesn't want to upload a new one, just like the app keeps the values for EditText fields:
private void saveInventory() {
// Read from input fields
// Use trim to eliminate leading or trailing white space
String nameString = mNameEditText.getText().toString().trim();
String infoString = mAdditionalInfoEditText.getText().toString().trim();
String priceString = mPriceEditText.getText().toString().trim();
String quantityString = mQuantityTextView.getText().toString().trim();
String image = actualUri.toString(); <---- error here
Image is selected in this method:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
// The ACTION_OPEN_DOCUMENT intent was sent with the request code READ_REQUEST_CODE.
// If the request code seen here doesn't match, it's the response to some other intent,
// and the below code shouldn't run at all.
if (requestCode == SELECT_AN_IMAGE_REQUEST && resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter. Pull that uri using "resultData.getData()"
if (resultData != null) {
actualUri = resultData.getData();
mPhotoImageView.setImageURI(actualUri);
}
}
}
I believe that saving the activity state and restoring it might solve the problem (code below), but I don't know where to paste it in my code....
// Save the activity state when it's going to stop.
// #Override
// protected void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState);
// outState.putParcelable("actualUri", actualUri);
// }
// Recover the saved state when the activity is recreated.
// #Override
// protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
// super.onRestoreInstanceState(savedInstanceState);
// actualUri= savedInstanceState.getParcelable("actualUri");
// }
Probably someone knows how to keep same image upon saving the editor activity. Thank you.
P.S. The finish activity quits the editor, and returns to Catalog. All data is saved in Database - apart from image.
#Override public boolean onOptionsItemSelected(MenuItem item) {
// User clicked on a menu option in the app bar overflow menu switch (item.getItemId())
{ // Respond to a click on the "Save" menu option case R.id.action_save:
// Save to database saveInventory();
// Exit activity finish(); return true;
I have solved the isssue.
I had to remove the line that causes an error in 'Save' class, and create a separate validation in this class:
if (actualUri== null) {
Toast.makeText(this, getString(R.string.image_required), Toast.LENGTH_SHORT).show();
return hasAllRequiredValues;
} else {
values.put(InventoryEntry.COLUMN_INVENTORY_IMAGE, actualUri.toString());
}
I have a share button in my application, whose purpose is to share a string of information with user selected app (for example twitter or gmail). The problem is when you click the share button, the share window appears and the app becomes black in the background. After done sharing, the user is returned to menu / home screen and has to open the app again in order to continue using it from where they left.
What I need is to go back to my app after done sharing.
This is the OnClickListener I used:
shareButton.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View view) {
String text = mContext.getString(R.string.shareText) + " " + profileInfo.getName() + " " + mContext.getString(R.string.shareText2);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, text);
shareIntent.setType("text/plain");
startActivity(shareIntent);
}
}
);
What am I doing wrong here? Any help is appreciated.
Use startActivityForResult() instead of startActivity(). This will return to the starting Activity after the Intent action is completed. See the Getting a Result from an Activity post for an example.
And wait for response by overriding onActivityResult() method :
#Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub if(requestCode == 0) {
// You will get callback here when email activity is exited
// perform you task here
}
There is noting wrong with your share intent code which you mentioned above, although there could be some thing wrong with your onPause() method, you probably doing some thing there, try to debug the code after adding logs So you can track the issue OR put your activity code here So exact issue can be identified.
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);
}
});
First post, so please go easy.
I have an app with a handful of tabs, the first is opened on running the app.
One of the tabs is 'My Account' (a ListActivity) showing account options. I switch to this and if the user is not logged in, it, in turn, runs a UserLogon activity using the following:
Intent logonActivity = new Intent(getBaseContext(), UserLogon.class);
startActivityForResult(logonActivity, 0);
To catch the result, I use the following code:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 0){
MainBlock ta = (MainBlock) this.getParent();
TabHost th = ta.getMyTabHost();
th.setCurrentTab(0);
finish();
}
if (requestCode == 100)
{
showAccountOptions();
}
}
In the UserLogon class, I have the usual fare; TextView's and Button's. The intention is that if the user cancels the login, it will revert to the initial tab, or if login is successful, it will show the Account Options. And this is indeed what does happen.
All good so far.
The problem I'm having is that if I cancel the login and return to the first tab, when I select the Accounts tab again, I'm not presented with the UserLogon activity. I was under the impression that finish() would close the UserLogon activity, and the Accounts activity but it would appear not.
So, my question is simply, how do I, in effect, restart the Accounts activity so that the user would be presented with the option to login once more.
We're good people and all willing to help ;-) I'll give it a shot. Still, I'm not quite sure I get that all right.
Basically you have an TabActivity which you setup and where you do something like that:
myTabHost.setOnTabChangedListener(new OnTabChangeListener(){
#Override
public void onTabChanged(String tabId) {
if (tabId.equals("account") && !loggedIn) {
Intent logonActivity = new Intent(getBaseContext(), UserLogon.class);
startActivityForResult(logonActivity, 0);
}
}});
You're basically saying that the first Activity start of UserLogon works, but the second one doesn't, right? Did you debugged to that point to check whether you reach the code which starts the activity again?
Update based on comment
Your UserLogon should always provide a result information, here's a blueprint:
public class UserLogon extends Activity {
public void onCreate(Bundle bundle) {
// ... do something ...
// if activity is canceled this will be the "last" result
setResult(RESULT_CANCELED);
}
public void checkLoginOrSomethingLikeThat() {
Intent result = new Intent();
// put your results in this intent
setResult(RESULT_OK, intent);
// close activity since we have the information we need
finish();
}
}
The parent activity which is waiting for the result should do it like that:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// it's good practice to use a constant for 0 like LOGIN_REQUEST
// otherwise you will ask yourself later "What the hell did 0 stand for?"
if(requestCode == 0){
if (resultCode == RESULT_OK) {
// get needed data from data intent and react on that
} else {
// no success, react on that
}
}
// ... I don't know what your resultCode 100 is but handle itwith the same pattern
}
I have an Android app which I use to register users on my web site. My first task is to register a user if my shared preferences file shows there is no registered user information.
If my app has a registered user, I provide the following code to simply and automatically switch to a "homepage" activity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.signin);
if( USERPREFERENCES.getString(USERPREFERENCES_USERMAIL, "") == null && USERPREFERENCES.getString(USERPREFERENCES_USERID, "") == null && USERPREFERENCES.getString(USERPREFERENCES_USERNAME, "") == null){
//setContentView(R.layout.signin);
Toast.makeText(SignIn.this, "testing...", Toast.LENGTH_LONG).show();
}else{
Intent intent = new Intent(SignIn.this, Confirmed.class);
startActivity(intent);
}
... other code
So, from my default activity, signin.java, the app will either switch to the Confirmed activity or stay on and display the signin activity.
My problem is, when the system works and I get switched to the the Confirmed activity, I provide a logout onclick listener which is below:
signout.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
//USERPREFERENCES.cl
Toast.makeText(Confirmed.this, "signout responding!", Toast.LENGTH_LONG).show();
USERPREFERENCES.edit().clear().commit();
}
});
It responds and clears all my shared preferences variables. But, when I use my menu to manually switch to the sign-in activity, I still get switched back to the Confirmed activity.
This happens even though I can confirm the variables are empty.
This hardly ever will be true:
USERPREFERENCES.getString(USERPREFERENCES_USERMAIL, "") == null
What if you use this instead?
if( USERPREFERENCES.getString(USERPREFERENCES_USERMAIL, null) == null && USERPREFERENCES.getString(USERPREFERENCES_USERID, null) == null && USERPREFERENCES.getString(USERPREFERENCES_USERNAME, null) == null){
//setContentView(R.layout.signin); TRY TO AVOID DOING THIS THING!!!!!
Toast.makeText(SignIn.this, "testing...", Toast.LENGTH_LONG).show();
}else...
Also, as a recommendation... instead of being switching between activities... what if you create just a Signing.java activity and put a ViewFlipper in its layout. That way your app will be not only faster but also easier to maintain.
This is Because When you will switch back to LoginActivity, this will be resumed instead of being created , Means your Login code which you written inOnCreate will not be called because Dthis time Overrider OnResume has been called , not onCreate .
So either write this code again in onResume or call finish() before moving to second activity , so that next time it will call onCreate()
If you navigate back to the first activity, the onCreate is not called again (unless the activity was destroyed for lack of resources). Move the authentication code in onResume.