I want to update my app immediately every time I publish an update. I followed the instructions from the official android documentation on how to test the auto update feature , but nothing happens. I placed some logs in order to check whether these functions initialize, but the logcat shows nothing either. Is this a problem in my syntax, or should I place these functions somewhere else? Currently, all my update code is written inside the main, starting class of the app.
On create method of the class
private static final int REQUEST_APP_UPDATE = 560;
private AppUpdateManager appUpdateManager;
private InstallStateUpdatedListener installStateUpdatedListener;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
appUpdateManager = AppUpdateManagerFactory.create(this);
installStateUpdatedListener = new
InstallStateUpdatedListener() {
#Override
public void onStateUpdate(InstallState state) {
if (state.installStatus() == InstallStatus.DOWNLOADED){
} else if (state.installStatus() == InstallStatus.INSTALLED){
if (appUpdateManager != null){
appUpdateManager.unregisterListener(installStateUpdatedListener);
}
} else {
Log.i(TAG, "InstallStateUpdatedListener: state: " + state.installStatus());
}
}
};
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
Log.d("TAG", "here");
// Checks that the platform will allow the specified type of update.
if ((appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE)
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE))
{
// Request the update.
try {
Log.d("TAG", "here");
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
this,
REQUEST_APP_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
appUpdateManager.registerListener(installStateUpdatedListener);
...
On resume and on stop handling methods:
#Override
protected void onResume() {
super.onResume();
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
if (appUpdateInfo.updateAvailability()
== UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
this,
REQUEST_APP_UPDATE);
Log.d("TAG", "tu");
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
}
#Override
protected void onStop() {
super.onStop();
if (appUpdateManager != null) {
appUpdateManager.unregisterListener(installStateUpdatedListener);
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_APP_UPDATE) {
if (resultCode != RESULT_OK) {
Log.d("TAG", "Update flow failed! Result code: " + resultCode);
// If the update is cancelled or fails,
// you can request to start the update again.
}
}
}
I suggest you to move all your AppUpdateInfo retrieving to onResume() since it is more reliable entry point of an activity (for example, if Activity has came to background and then was opened again by user). OnCreate method will be called only if activity was destroyed, since that, you may not see update dialog after reopening application if it was minimized.
#Override
public void onResume() {
super.onResume();
appUpdateManager.getAppUpdateInfo().addOnSuccessListener( info -> {
boolean isStalledUpdate = info.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS;
boolean isReadyForUpdate =
info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE;
if (isReadyForUpdate || isStalledUpdate) {
appUpdateManager.startUpdateFlowForResult(
info,
AppUpdateType.IMMEDIATE,
this,
REQ_CODE
);
}
}
Sometimes app is not automatically synchronized with Play Store updates, so you need to do the following:
Make sure you have an update available manually - go to Play Store, check for updates and make sure that you have an update available for your application.
After that open your app (your activity, that calls update manager in onResume) and you will see immediate update dialog.
Also, one tip for you - make your Activity implement InstallStateUpdateListener and override method onStateUpdate like this to handle different events.
#Override
public void onResume() {
// All previous logic
// If update is available or was stalled call this
appUpdateManager.registerListener(this);
}
#Override
public void onStateUpdate(InstallState state) {
if (state == null) return;
switch (state.installStatus()) {
case InstallStatus.INSTALLED:
appUpdateManager.unregisterListener(this)
return;
case InstallStatus.FAILED:
appUpdateManager.unregisterListener(this)
return;
case InstallStatus.CANCELED:
appUpdateManager.unregisterListener(this)
return;
default:
// provide your own logic
return;
}
}
This will help you to avoid calling separate instance of listener in activity lifecycle methods.
The code that I provided was working, the problem of not updating was a matter of the device itself not knowing whether an update is available. After refreshing the update list on the Play Store, the app update manager initialized the auto update window when starting the app.
Related
#Override
protected void onCreate(Bundle savedInstanceState) {
...
appUpdateManager = AppUpdateManagerFactory.create(this);
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
// For a flexible update, use AppUpdateType.FLEXIBLE
&& appUpdateInfo.isUpdateTypeAllowed(IMMEDIATE)) {
// Request the update.
try {
appUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// Or 'AppUpdateType.FLEXIBLE' for flexible updates.
IMMEDIATE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MY_REQUEST_CODE);
}
catch(IntentSender.SendIntentException e){
e.printStackTrace();
}
}
});
}
#Override
protected void onResume() {
super.onResume();
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
if (appUpdateInfo.updateAvailability()
== UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
try {
appUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// Or 'AppUpdateType.FLEXIBLE' for flexible updates.
IMMEDIATE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MY_REQUEST_CODE);
}
catch(IntentSender.SendIntentException e){
e.printStackTrace();
}
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == MY_REQUEST_CODE) {
if (resultCode != RESULT_OK) {
Log.e("Main", "onActivityResult: app download failed");
}
}
}
This is the entire code from my MainActivity. I expected this to work as is as it comes right from the android docs. But it doesn't seem to do anything. To test it I put out a quick update and waited until it was approved. I opened it hoping to be blocked by a full screen saying I must update, but I could use the app freely.
This actually just randomly worked about 36 hours later when I opened the app again.
There must be a 24 hour delay or so from when update goes live til when the API will pick it up.
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 am fairly new to android development, and am having major troubles understanding how to implement google in app billing. I have read the official google documentation, read through quite a few tutorials, etc. The one that i have found that is the most simple to me is the following. Even then i am struggling to get it working.
http://redappz.com/micro-transactions-tutorial-iap-for-android/
What i have done is, imported all helper classes from trivial drive. Have a google dev account with a signed apk.
The logic i want for my application is when a purchase is successful in Activity A. Activity B displays a button.
public class WorkoutPlan1 extends AppCompatActivity {
private Button WorkoutPlan1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_workoutplan1);
WorkoutPlan1 = (Button) findViewById(R.id.Workout1MoreInfo);
Base code with out any google billing stuff. Basic button just to run the google billing off.
public class WorkoutPlan1 extends AppCompatActivity {
private Button WorkoutPlan1;
//*************************************************************************************************************************
// Debug tag, for logging
static final String TAG = "test";
// SKUs for our products: the premium upgrade (non-consumable)
static final String SKU_PREMIUM = "android.test.purchased";
// Does the user have the premium upgrade?
boolean mIsPremium = false;
// (arbitrary) request code for the purchase flow
static final int RC_REQUEST = 1;
// The helper object
IabHelper mHelper;
//*************************************************************************************************************************
#Override
protected void onCreate(Bundle savedInstanceState) {
//*************************************************************************************************************************
String base64EncodedPublicKey = "KEY GOES HERE FROM MY GOOGLE DEV";
//It is recommended to add more security than just pasting it in your source code;
mHelper = new IabHelper(this, base64EncodedPublicKey);
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);
}
// Hooray, IAB is fully set up!
// mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
//*************************************************************************************************************************
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_workoutplan1);
WorkoutPlan1 = (Button) findViewById(R.id.Workout1MoreInfo);
}
//*************************************************************************************************************************
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
if (result.isFailure()) {
Log.d(TAG, "Failed to query inventory: " + result);
return;
}
else {
Log.d(TAG, "Query inventory was successful.");
// does the user have the premium upgrade?
mIsPremium = inventory.hasPurchase(SKU_PREMIUM);
// update UI accordingly
WorkoutPlan1.setVisibility(View.VISIBLE);
Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
}
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
}
};
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
if (result.isFailure()) {
Log.d(TAG, "Error purchasing: " + result);
return;
}
else if (purchase.getSku().equals(SKU_PREMIUM)) {
// give user access to premium content and update the UI
WorkoutPlan1.setVisibility(View.VISIBLE);
}
}
};
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
} else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
Im aware i dont have an onclick listener on the button. But i dont even know how i would invoke the payment / purchase flow.
Any help AT ALL to point me in the right direction would be so appreciated, as at this point i just feel lost.
Thank you everyone
You would use the IabHelper object to invoke the purchase flow. For instance:
try {
mHelper.launchPurchaseFlow(this, SKU_BUNDLE, RC_REQUEST, mPurchaseFinishedListener,
payload);
} catch (IabAsyncInProgressException e) {
complain("Error launching purchase flow. Another async operation in progress.");
}
... So I would include this code snippet inside an onClickListener for your button. And then you would handle the purchases' response through the OnIabPurchaseFinishedListener.
P.S. I'm answering this with my mobile device, so excuse the poor formatting 😅😅😅
I have a Fragment MainFragment and I do:
Intent i = new Intent(getActivity(), PersonActivity.class);
startActivityForResult(i, 0);
The activity starts ok and it starts its own PersonFragment and inside the PersonFragment I do:
#Override
public void onDestroy() {
super.onDestroy();
Intent i = new Intent();
i.putExtra(PERSON_ID_EXTRA, getPersonId());
i.putParcelableArrayListExtra(PERSON_CONTACT_LIST, (ArrayList<? extends Parcelable>) contactFriends);
getActivity().setResult(Activity.RESULT_OK, i);
}
Back in my MainFragment I do:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if ( requestCode != 0) {
return;
}
int personId = data.getIntExtra(PERSON_ID_EXTRA, -1);
List<Person> contacts = data.getParcelableArrayListExtra(PERSON_CONTACT_LIST);
for(Person p:contacts) {
Log.d("APP", p.getFullName());
}
}
I see that the code goes to onActivityResult but the data is null. What am I messing up here?
Update:
I see that pressing back button does not call onDestroy().
But where am all examples I saw used getActivity.finish() and I don't want to finish the activity. Only when the user presses e.g. back send the data
Update2:
I added the following and I go through that code but the Intent data in the result onActivityResult is still null
#Override
public void onPause() {
super.onPause();
Intent i = new Intent();
i.putExtra(PERSON_ID_EXTRA, getPersonId());
i.putParcelableArrayListExtra(PERSON_CONTACT_LIST, (ArrayList<? extends Parcelable>) contactFriends);
getActivity().setResult(Activity.RESULT_OK, i);
}
From the Activity documentation about finish:
Call this when your activity is done and should be closed. The
ActivityResult is propagated back to whoever launched you via
onActivityResult().
From the documentation about onActivityResult:
Called when an activity you launched exits, giving you the requestCode
you started it with, the resultCode it returned, and any additional
data from it.
So, onActivityResult will only be called when the second activity finishes.
If you don't want to finish your PersonActivity to send the result to your main activity, then you may want to start another intent to send the data or pass the data using static fields (not a best practice at all).
In your case, to set your result when you press the back button, you can write a code like this:
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
Intent i = new Intent();
i.putExtra(PERSON_ID_EXTRA, getPersonId());
i.putParcelableArrayListExtra(PERSON_CONTACT_LIST, (ArrayList<? extends Parcelable>) contactFriends);
getActivity().setResult(Activity.RESULT_OK, i);
finish();
return true;
}
return false;
}
Follow up based on the comments:
The code for the finish() on the Activity class looks as follows:
// Call this when your activity is done
// and should be closed. The ActivityResult
// is propagated back to whoever launched
// you via onActivityResult().
public void finish() {
if (mParent == null) {
int resultCode;
Intent resultData;
synchronized (this) {
resultCode = mResultCode;
resultData = mResultData;
}
if (false) Log.v(TAG, "Finishing self: token=" + mToken);
try {
if (resultData != null) {
resultData.prepareToLeaveProcess();
}
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData)) {
mFinished = true;
}
} catch (RemoteException e) {
// Empty
}
} else {
mParent.finishFromChild(this);
}
}
Here you can see that is the responsibility for setting the result values lays on the finish() method.
Then, before the Activity is destroyed, the onDestroy() method is called. That is the reason why setting the result of the Activity on the onDestroy() method won't work.
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