I use the new Google drive api and I can't get All folders from my google drive, I only get the folders that I create with the google drive api...
Anybody know why happens this?
this is my code:
#Override
protected void onResume() {
super.onResume();
if (mGoogleApiClient == null) {
// Create the API client and bind it to an instance variable.
// We use this instance as the callback for connection and connection
// failures.
// Since no account name is passed, the user is prompted to choose.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
// Connect the client. Once connected, the camera is launched.
mGoogleApiClient.connect();
}
/**
* Handles resolution callbacks.
*/
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_RESOLUTION && resultCode == RESULT_OK) {
mGoogleApiClient.connect();
}
}
/**
* Called when activity gets invisible. Connection to Drive service needs to
* be disconnected as soon as an activity is invisible.
*/
#Override
protected void onPause() {
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
super.onPause();
}
/**
* Called when {#code mGoogleApiClient} is connected.
*/
#Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "GoogleApiClient connected");
rootFolder = Drive.DriveApi.getRootFolder(mGoogleApiClient);
rootFolder.listChildren(getGoogleApiClient()).setResultCallback(pruebaChildren);
}
ResultCallback<DriveApi.MetadataBufferResult> pruebaChildren = new
ResultCallback<DriveApi.MetadataBufferResult>() {
#Override
public void onResult(DriveApi.MetadataBufferResult metadataBufferResult) {
if (!metadataBufferResult.getStatus().isSuccess()) {
showMessage("Problem while retrieving files");
return;
}
Log.i(TAG,"got root folder");
MetadataBuffer buffer = metadataBufferResult.getMetadataBuffer();
Log.i(TAG,"Buffer count " + buffer.getCount());
if(buffer.getCount() == 0){
createFolderApp();
}
else{
for(Metadata m : buffer){
Log.i(TAG,"Metadata name " + m.getTitle() + "(" + (m.isFolder() ? "folder" : "file") + ")");
if (m.isFolder() && m.getTitle().equals(TAG)){
Log.i(TAG,"APP FOLDER FOUND");
Drive.DriveApi.getFolder(mGoogleApiClient, m.getDriveId())
.listChildren(mGoogleApiClient)
.setResultCallback(foreachAppAplication);
}
}
}
return;
}
};
And now I want see all folders in rootFolder Drive, I try the requestSync() but the result is same... I need help please!
And another question: How I can set the AppFolder? I only see getAppFolder but How I can set ??
Thanks
By design, GDAA supports only the FILE scope, i.e. it will find / list only folders / files created by the Android app.
There are 2 ways around it:
Use one of the intents of the GDAA, basically letting the user pick the file / folder. That way it will become available to your app.
Use a different API, the REST Api, which supports the DRIVE scope, giving your app full set of files / folders.
In case you want to study how the two APIs behave, I've put two different demos on the Github (the REST and the GDAA CRUD demo wrappers).
The second part of your question does not have answer. You don't set the app folder, you can only get it's DriveFolder id. You use it to create / retrieve objects.
DriveFolder appFldr = Drive.DriveApi.getAppFolder(mGooleApiClient);
appFldr.createFile(...);
appFldr.createFolder(...);
appFldr.listChildren(...);
appFldr.queryChildren(...);
... and don't forget to add the SCOPE_APPFOLDER scope
Good Luck
Related
My problem is that I do not manage to create a directory on a SD card that is plugged in an Android portable device.
Below is the Java code I am trying to get to work: I am trying to create the directory sable under /storage/BF4F-1107/:
public class AnActivity extends AppCompatActivity
{
private static final int N_CREATE_DIRECTORY = 1;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// SD card
String s_sdCardStorage = "/storage/BF4F-1107/";
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("file/*");
intent.putExtra(Intent.EXTRA_TITLE, s_destFilePath);
startActivityForResult(intent, N_CREATE_DIRECTORY);
// HERE
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if(requestCode == N_CREATE_DIRECTORY)
{
if(data != null)
{
Uri uri = data.getData();
DocumentFile docFileSDCardStorage = DocumentFile.fromSingleUri(this, uri);
try
{
DocumentFile docFileDir = docFileSDCardStorage.createDirectory("sable");
}
catch(UnsupportedOperationException exn)
{
System.out.println(exn.getMessage());
}
}
}
}
}
What happens is:
the code in onCreate() is executed.
Then the program blocks at // HERE.
On the portable device, appears a "dialog" which shows /storage/BF4F-1107/ and a button Save which I "press".
Once "pressed", the code in onActivityResult() is executed.
But the result is that the directory sable under /storage/BF4F-1107/ is not created.
And the execution path goes through the catch clause, the exception UnsupportedOperationException is raised and null is printed at System.out.println(exn.getMessage());.
Edit 2: An empty file _storage_BF4F-1107_ is created under /storage/BF4F-1107/.
Can you help me make this code work?
Additionally, I would like the directory sable to be created silently.
I do not want to user to have to touch "Save".
I am trying to use the Storage Access Framework (https://developer.android.com/training/data-storage/shared/documents-files) because the mkdirs method of the java.io.File class doesn't work (I get permission denied exceptions) when I try to create a directory on the SD card.
Edit: my Android version is 6.0.1
Thank you.
Use ACTION_OPEN_DOCUMENT_TREE to let the user choose de SD card.
After that you can create as many files and directorys in the choosen directory.
If you only want to create one file with SAF use ACTION_CREATE_DOCUMENT where the user chooses the location and file name.
I was surfing in Android code because I wanted to see what is into Activity.finish() method.
I just wanted to have the confirmation that in Activity.finish() there would be a call to onDestroy() method.
But what I found in this method (and in many others) was:
public void finish() {
throw new RuntimeException("Stub!");
}
So WHERE Can I find the code that really destroys the Activity?
Thanks!
This is because source code is not found in SDK.
To see the source code, you need to download source for Android SDK, so Android studio can display the respective code.
I don't know where you looked, but the code for finish() is this
/**
* 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() {
finish(DONT_FINISH_TASK_WITH_ACTIVITY);
}
which calls the private implementation
/**
* Finishes the current activity and specifies whether to remove the task associated with this
* activity.
*/
private void finish(int finishTask) {
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(this);
}
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData, finishTask)) {
mFinished = true;
}
} catch (RemoteException e) {
// Empty
}
} else {
mParent.finishFromChild(this);
}
}
Important here is ActivityManagerNative.getDefault().finishActivity which you can find at line 3359 in this file https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ActivityManagerNative.java
If you want to dive deeper, you can just follow the trail.
You are checking in .class not .java file.
I'm trying to capture the download links of some public files from my google drive, but the method metadataBuffer.getCount() always returns 0.
I saw in other posts that the problem are the scopes
GoogleApiClient.Builder builder = new GoogleApiClient.Builder(context);
builder.addApi(Drive.API);
builder.addScope(Drive.SCOPE_FILE);
builder.setAccountName(EXTRA_ACCOUNT_NAME);
builder.addConnectionCallbacks(connectionCallbacks);
builder.addOnConnectionFailedListener(connectionFailedListener);
googleApiClient = builder.build();
googleApiClient.connect(); // always connection success
but there are only two optons "scopes" apparently Drive.SCOPE_FILE and Drive.SCOPE_FOLDER.
private ResultCallback<DriveApi.MetadataBufferResult> metadataBufferCallback = new ResultCallback<DriveApi.MetadataBufferResult>() {
#Override
public void onResult(#NonNull DriveApi.MetadataBufferResult metadataBufferResult) {
MetadataBuffer metadataBuffer = metadataBufferResult.getMetadataBuffer();
int count = metadataBuffer.getCount(); // <--- always return 0
Log.d(TAG, "count " + count);
}
};
what should I do to view all files in a public folder using Drive API v3?
I am working on an Android app that records video and allows the user to upload it directly to YouTube using the YouTube Data API v3.
I have set up my app in Google's API console. Under services, I have YouTube Data API v3 enabled. Under API access I have both a section "Client ID for installed applications" (including a Client ID and Client Secret) and a section "Simple API Access" -> "Key for Android apps (with certificates)" (which includes an API key and an "Android Apps" section, which is left blank for now, i.e. allow all Android apps, but I have tried it with setting my android key).
I have based my code from a number of places, primarily:
https://developers.google.com/youtube/v3/code_samples/java#upload_a_video
and
https://code.google.com/p/google-api-java-client/source/browse/tasks-android-sample/src/main/java/com/google/api/services/samples/tasks/android/TasksSample.java?repo=samples
The upload initialises OK, starts the AsyncTask, but then I get an IOException thrown saying:
{
"code": 403,
"errors": [
{
"domain": "usageLimits",
"message": "Access Not Configured",
"reason": "accessNotConfigured"
}
],
"message": "Access Not Configured"
}
Similar SO posts suggest it is to do with my Google API console settings, but I can't find anything wrong. Any suggestions? I wonder if it is because I am not providing my client ID or secret anywhere ...
Thanks.
My code runs from a fragment containing a list of videos. The relevant sections are:
-- Init
public class UploadFragment extends Fragment {
private static GoogleAccountCredential credential;
private static final HttpTransport transport = AndroidHttp.newCompatibleTransport();
private static final JsonFactory jsonFactory = new GsonFactory();
public YouTube youtube;
List<String> scopes = Lists.newArrayList(YouTubeScopes.YOUTUBE_UPLOAD);
private static String VIDEO_FILE_FORMAT = "video/*";
static final int REQUEST_GOOGLE_PLAY_SERVICES = 0;
static final int REQUEST_AUTHORIZATION = 1;
static final int REQUEST_ACCOUNT_PICKER = 2;
-- Setup credential and youtube
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
credential = googleAccountCredential(scopes);
youtube = new YouTube.Builder(transport, jsonFactory, credential)
.setApplicationName("MyAppName")
.build();
...
}
-- On button click, initiate upload
#Override void onClick(View v) {
...
if (hasGooglePlayServices()) {
uploadYouTubeVideos();
...
}
-- Build credential
/**
* Get the credential to authorize the installed application to access user's protected data.
*
* #param scopes list of scopes needed to run YouTube upload.
*/
private static GoogleAccountCredential googleAccountCredential(List<String> scopes) throws Exception {
credential = GoogleAccountCredential.usingOAuth2(context, scopes)
.setSelectedAccountName(PreferenceManager.getAccountName());
return credential;
}
-- Request an account from the user
/**
* Fire intent to get user to choose account
* Return to onActivityResult
*/
private void chooseAccount() {
startActivityForResult(credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}
-- On return from the user choosing and account
-- / requesting authorization
/**
* Returns from chooseAccount and from request authorization
*/
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_AUTHORIZATION:
if (resultCode == Activity.RESULT_OK) {
uploadYouTubeVideos();
} else {
chooseAccount();
}
break;
case REQUEST_ACCOUNT_PICKER:
if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) {
String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
credential.setSelectedAccountName(accountName);
PreferenceManager.setAccountName(accountName);
uploadYouTubeVideos();
}
}
break;
}
}
-- Called multiple times depending on what information we have
-- account, authorization, etc.
/**
* Uploads user selected video to the user's YouTube account using OAuth2
* for authentication.
*
* #param videoFile file to be uploaded
*/
public void uploadYouTubeVideos() {
if (credential.getSelectedAccountName() == null) {
chooseAccount();
} else {
File videoFile = getVideoFile();
Insert videoInsert = prepareUpload(videoFile);
new VideoUploadAsyncTask().execute(videoInsert);
}
}
-- Prepare the upload
-- Puts everything together
/**
* Prepare upload. Just leaves execute to be run in AsyncTask.
*
* #param videoFile file to be uploaded
* #return
*/
public Insert prepareUpload( File videoFile ) {
try {
// Add extra information to the video before uploading.
Video videoObjectDefiningMetadata = new Video();
// Set the video to public (default).
VideoStatus status = new VideoStatus();
status.setPrivacyStatus("public");
videoObjectDefiningMetadata.setStatus(status);
// We set a majority of the metadata with the VideoSnippet object.
VideoSnippet snippet = new VideoSnippet();
// Video file name.
snippet.setTitle(videoFile.getName());
snippet.setDescription("Test description");
// Set keywords.
List<String> tags = new ArrayList<String>();
tags.add("test");
snippet.setTags(tags);
// Set completed snippet to the video object.
videoObjectDefiningMetadata.setSnippet(snippet);
InputStreamContent mediaContent = new InputStreamContent(
VIDEO_FILE_FORMAT, new BufferedInputStream(new FileInputStream(videoFile)));
mediaContent.setLength(videoFile.length());
/*
* The upload command includes: 1. Information we want returned after file is successfully
* uploaded. 2. Metadata we want associated with the uploaded video. 3. Video file itself.
*/
YouTube.Videos.Insert videoInsert = youtube.videos()
.insert("snippet,statistics,status", videoObjectDefiningMetadata, mediaContent);
// Set the upload type and add event listener.
MediaHttpUploader uploader = videoInsert.getMediaHttpUploader();
/*
* Sets whether direct media upload is enabled or disabled. True = whole media content is
* uploaded in a single request. False (default) = resumable media upload protocol to upload
* in data chunks.
*/
uploader.setDirectUploadEnabled(false);
MediaHttpUploaderProgressListener progressListener = new MediaHttpUploaderProgressListener() {
public void progressChanged(MediaHttpUploader uploader) throws IOException {
switch (uploader.getUploadState()) {
case INITIATION_STARTED:
Log.d(TAG, "Upload file: Initiation Started");
break;
case INITIATION_COMPLETE:
Log.d(TAG, "Upload file: Initiation Completed");
break;
case MEDIA_IN_PROGRESS:
Log.d(TAG, "Upload file: Upload in progress");
Log.d(TAG, "Upload file: Upload percentage: " + uploader.getProgress());
break;
case MEDIA_COMPLETE:
Log.d(TAG, "Upload file: Upload Completed!");
break;
case NOT_STARTED:
Log.d(TAG, "Upload file: Upload Not Started!");
break;
}
}
};
uploader.setProgressListener(progressListener);
return videoInsert;
} catch (FileNotFoundException e) {
Log.e(TAG, "File not found: " + e.getMessage());
return null;
} catch (IOException e) {
Log.e(TAG, "IOException: " + e.getMessage());
return null;
}
}
-- Require Google play services
/**
* Pop up dialog requesting user to download Google Play Services.
* Returns to onActivityResult
*/
void showGooglePlayServicesAvailabilityErrorDialog(final int connectionStatusCode) {
getActivity().runOnUiThread(new Runnable() {
public void run() {
Dialog dialog =
GooglePlayServicesUtil.getErrorDialog(connectionStatusCode, getActivity(),
REQUEST_GOOGLE_PLAY_SERVICES);
dialog.show();
}
});
}
-- AsyncTask that runs execute on the upload
public class VideoUploadAsyncTask extends AsyncTask<Insert, Void, Void> {
#Override
protected Void doInBackground( Insert... inserts ) {
Insert videoInsert = inserts[0];
try {
Video returnVideo = videoInsert.execute();
} catch (final GooglePlayServicesAvailabilityIOException availabilityException) {
showGooglePlayServicesAvailabilityErrorDialog(
availabilityException.getConnectionStatusCode());
} catch (UserRecoverableAuthIOException userRecoverableException) {
startActivityForResult(
userRecoverableException.getIntent(), UploadFragment.REQUEST_AUTHORIZATION);
} catch (IOException e) {
Log.e(TAG, "IOException: " + e.getMessage());
}
return null;
}
}
}
The answer provided by #Ibrahim was almost correct for me. What I needed to do was edit my API configuration. However, it was not the "Simple API access" section I needed to edit, it was the settings after clicking the button "Create another client Id".
Then I could select "Installed application" -> "Android". After inputting my package name and SHA1, and waiting 15 minutes, my app worked as expect. I also have the "Simple API access" set up. I am not sure if you need both or not.
Yes, YouTube Direct Lite for Android is similar. You have to configure simple API access with your SHA1 key. Explains here.
Lets say my Android App version 0.1 is installed currently on the User's phone. Everytime they launch my App I want to check if there is a different version available in the Android Market let's say this version is 0.2. If there is a mismatch between these two version I want to show a dialog box prompting the user to Upgrade the App.
I totally understand there exists a notification procedure from Android Market itself to the users but as far as my Analytics data is concerned it is not very effective in reminding users to upgrade to the new version of the App.
Any insight would be very helpful. Thanks StackOverflowers, you guys rock!
As of 2019 the best way for updating your app is to use In-app updates provided by Play Core library (1.5.0+). It works for Lollipop and newer, but let's be fair, Kit-Kat is less than 7% as of today and soon will be gone forever. You can safely run this code on Kit-Kat without version checks, it won't crash.
Official documentation: https://developer.android.com/guide/app-bundle/in-app-updates
There are two types of In-app updates: Flexible and Immediate
Flexible will ask you nicely in a dialog window:
whereas Immediate will require you to update the app in order to continue using it with full-screen message (this page can be dismissed):
Important: for now, you can't choose which type of update to roll out in your App Release section on Developer Play Console. But apparently, they will give us that option soon.
From what I've tested, currently, we're getting both types available in onSuccessListener.
So let's implement both types in our code.
In module build.gradle add the following dependency:
dependencies {
implementation 'com.google.android.play:core:1.6.1'//for new version updater
}
In MainActivity.class:
private static final int REQ_CODE_VERSION_UPDATE = 530;
private AppUpdateManager appUpdateManager;
private InstallStateUpdatedListener installStateUpdatedListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkForAppUpdate();
}
#Override
protected void onResume() {
super.onResume();
checkNewAppVersionState();
}
#Override
public void onActivityResult(int requestCode, final int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
switch (requestCode) {
case REQ_CODE_VERSION_UPDATE:
if (resultCode != RESULT_OK) { //RESULT_OK / RESULT_CANCELED / RESULT_IN_APP_UPDATE_FAILED
L.d("Update flow failed! Result code: " + resultCode);
// If the update is cancelled or fails,
// you can request to start the update again.
unregisterInstallStateUpdListener();
}
break;
}
}
#Override
protected void onDestroy() {
unregisterInstallStateUpdListener();
super.onDestroy();
}
private void checkForAppUpdate() {
// Creates instance of the manager.
appUpdateManager = AppUpdateManagerFactory.create(AppCustom.getAppContext());
// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
// Create a listener to track request state updates.
installStateUpdatedListener = new InstallStateUpdatedListener() {
#Override
public void onStateUpdate(InstallState installState) {
// Show module progress, log state, or install the update.
if (installState.installStatus() == InstallStatus.DOWNLOADED)
// After the update is downloaded, show a notification
// and request user confirmation to restart the app.
popupSnackbarForCompleteUpdateAndUnregister();
}
};
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
// Request the update.
if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(installStateUpdatedListener);
// Start an update.
startAppUpdateFlexible(appUpdateInfo);
} else if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) ) {
// Start an update.
startAppUpdateImmediate(appUpdateInfo);
}
}
});
}
private void startAppUpdateImmediate(AppUpdateInfo appUpdateInfo) {
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MainActivity.REQ_CODE_VERSION_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
private void startAppUpdateFlexible(AppUpdateInfo appUpdateInfo) {
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MainActivity.REQ_CODE_VERSION_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
unregisterInstallStateUpdListener();
}
}
/**
* Displays the snackbar notification and call to action.
* Needed only for Flexible app update
*/
private void popupSnackbarForCompleteUpdateAndUnregister() {
Snackbar snackbar =
Snackbar.make(drawerLayout, getString(R.string.update_downloaded), Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.restart, new View.OnClickListener() {
#Override
public void onClick(View view) {
appUpdateManager.completeUpdate();
}
});
snackbar.setActionTextColor(getResources().getColor(R.color.action_color));
snackbar.show();
unregisterInstallStateUpdListener();
}
/**
* Checks that the update is not stalled during 'onResume()'.
* However, you should execute this check at all app entry points.
*/
private void checkNewAppVersionState() {
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
//FLEXIBLE:
// If the update is downloaded but not installed,
// notify the user to complete the update.
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdateAndUnregister();
}
//IMMEDIATE:
if (appUpdateInfo.updateAvailability()
== UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
startAppUpdateImmediate(appUpdateInfo);
}
});
}
/**
* Needed only for FLEXIBLE update
*/
private void unregisterInstallStateUpdListener() {
if (appUpdateManager != null && installStateUpdatedListener != null)
appUpdateManager.unregisterListener(installStateUpdatedListener);
}
And we're done!
Testing.
Please read the docs so you will know how to test it properly with test tracks on Google Play.
Long story short:
Sign your app with the release certificate and upload it to the one of publishing tracks in Developer Play Console under App Releases (alpha/beta/other custom closed track).
In your release track page in the Manage Testers section create and add a list of testers and make sure you checked the checkbox! - this step is optional since your developer account email is also a testers account and you can use it for testing.
Under the list of testers you will find "Opt-in URL" - copy this url and give it to your testers or open it yourself. Go to that page and accept proposition for testing. There will be a link to the app. (You won't be able to search for the app in Play Store so bookmark it)
Install the app on your device by that link.
In build.gradle increment the version of defaultConfig { versionCode k+1 } and build another signed apk Build > Generate Signed Bundle / APK... and upload it to your publishing track.
Wait for... 1 hour? 2 hours? or more before it will be published on the track.
CLEAR THE CACHE of Play Store app on your device. The problem is that Play app caches details about installed apps and their available updates so you need to clear the cache. In order to do that take two steps:
7.1. Go to Settings > App > Google PLay Store > Storage > Clear Cache.
7.2. Open the Play Store app > open main menu > My apps & games > and there you should see that your app has a new update.
If you don't see it make sure that your new update is already released on the track (go to your bookmarked page and use it to open your apps listing on the Play Store to see what version is shown there). Also, when your update will be live you'll see a notification on the top right of your Developer Play Console (a bell icon will have a red dot).
Hope it helps.
The Android Market is a closed system and has only an unofficial api that might break at any point of time.
Your best bet is simply to host a file(xml, json or simple text) on a web server of yours in which you just have to update the current version of your app when you post it on the Market.
Your app will then only have to fetch that file at startup, checks wether currently installed app has a lower version number and displays a dialog to warn the user he is lagging.
Another option you can use, if you want to avoid having your backend server to store your current app version like it's suggested in the accepted answer, is to use Google Tag Manager (GTM).
If you're already using the Google Analytics SDK, you have the GTM in it also.
In GTM you can define a value in the container for your app that specifies your latest released version. For example:
{
"latestAppVersion": 14,
...
}
Then you can query that value when your app starts and show the user update dialog reminder if there's a newer version.
Container container = TagManager.getInstance(context).openContainer(myContainerId);
long latestVersionCode = container.getLong("latestAppVersion");
// get currently running app version code
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
long versionCode = pInfo.versionCode;
// check if update is needed
if(versionCode < latestVersionCode) {
// remind user to update his version
}
Take a look at this library that you can use to query the Android Market API
http://code.google.com/p/android-market-api/
You can use this Android Library: https://github.com/danielemaddaluno/Android-Update-Checker. It aims to provide a reusable instrument to check asynchronously if exists any newer released update of your app on the Store.
It is based on the use of Jsoup (http://jsoup.org/) to test if a new update really exists parsing the app page on the Google Play Store:
private boolean web_update(){
try {
String curVersion = applicationContext.getPackageManager().getPackageInfo(package_name, 0).versionName;
String newVersion = curVersion;
newVersion = Jsoup.connect("https://play.google.com/store/apps/details?id=" + package_name + "&hl=en")
.timeout(30000)
.userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
.referrer("http://www.google.com")
.get()
.select("div[itemprop=softwareVersion]")
.first()
.ownText();
return (value(curVersion) < value(newVersion)) ? true : false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
And as "value" function the following (works if values are beetween 0-99):
private long value(String string) {
string = string.trim();
if( string.contains( "." )){
final int index = string.lastIndexOf( "." );
return value( string.substring( 0, index ))* 100 + value( string.substring( index + 1 ));
}
else {
return Long.valueOf( string );
}
}
If you want only to verify a mismatch beetween versions, you can change:
"value(curVersion) < value(newVersion)" with "value(curVersion) != value(newVersion)"
For prompting Android App User to Update App if current version is not equal to market version, you should first check the app version on the market and compare it with the version of the app on the device. If they are different, it may be an update available. In this post I wrote down the code for getting the current version of market and current version on the device and compare them together. I also showed how to show the update dialog and redirect the user to the update page. Please visit this link: https://stackoverflow.com/a/33925032/5475941
My working Kotlin code for force App update:
const val FLEXIABLE_UPADTE: Int = 101
const val FORCE_UPDATE: Int = 102
const val APP_UPDATE_CODE: Int = 500
override fun onCreate {
// Get updateType from Webservice.
updateApp(updateType)
}
private fun updateApp(statusCode: Int) {
appUpdateManager = AppUpdateManagerFactory.create(this #MainActivity)
val appUpdateInfoTask = appUpdateManager ? .appUpdateInfo
appUpdateInfoTask ? .addOnSuccessListener {
appUpdateInfo - >
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
if ((statusCode == Constants.FORCE_UPDATE))
appUpdateManager ? .startUpdateFlowForResult(
appUpdateInfo, AppUpdateType.IMMEDIATE, this, Constants.APP_UPDATE_CODE
)
else if (statusCode == Constants.FLEXIABLE_UPADTE)
appUpdateManager ? .startUpdateFlowForResult(
appUpdateInfo, AppUpdateType.FLEXIBLE, this, Constants.FLEXIABLE_UPADTE
)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent ? ) {
try {
if (requestCode == Constants.APP_UPDATE_CODE && resultCode == Activity.RESULT_OK) {
if (resultCode != RESULT_OK) {
appUpdateCompleted()
}
}
} catch (e: java.lang.Exception) {
}
}
private fun appUpdateCompleted() {
Snackbar.make(
findViewById(R.id.activity_main_layout),
"An update has just been downloaded.",
Snackbar.LENGTH_INDEFINITE
).apply {
setAction("RESTART") {
appUpdateManager.completeUpdate()
}
setActionTextColor(resources.getColor(R.color.snackbar_action_text_color))
show()
}
}