Currently i'm implementing the in app update feature from android. I'm using the immediate update method, the problem that I'm facing is that when the UI prompting the user to update shows and the user does not click the update button instead they click the cross button. The UI for the app update just closes and user can continue using the app.
What I want is that when user click the cross button the app immediately closes, until user updates the app then they can use the app as usual. I also uses the java code for the android development.
public class LoginActivity extends AppCompatActivity {
private LoginViewModel loginViewModel;
public static final String MyPREFERENCES = "LoginPrefs" ;
public static final String Name = "nameKey";
public static final String User = "userKey";
public static final String con = "closed";
public static String error = "";
public static int userFlag = 0;
SharedPreferences sharedpreferences;
SharedPreferences.Editor editor;
public TextInputEditText usernameEditText;
public TextInputEditText passwordEditText;
private AppUpdateManager mAppUpdateManager;
private int RC_APP_UPDATE = 999;
private int inAppUpdateType;
private com.google.android.play.core.tasks.Task<AppUpdateInfo> appUpdateInfoTask;
private InstallStateUpdatedListener installStateUpdatedListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
loginViewModel = ViewModelProviders.of(this, new LoginViewModelFactory())
.get(LoginViewModel.class);
usernameEditText = findViewById(R.id.user);
passwordEditText = findViewById(R.id.pass);
final Button loginButton = findViewById(R.id.submitBTN);
final TextInputLayout userL = findViewById(R.id.userL);
final TextInputLayout passL = findViewById(R.id.passL);
final JellyToggleButton jtb = findViewById(R.id.jtb);
// Creates instance of the manager.
mAppUpdateManager = AppUpdateManagerFactory.create(this);
// Returns an intent object that you use to check for an update.
appUpdateInfoTask = mAppUpdateManager.getAppUpdateInfo();
//lambda operation used for below listener
//For flexible update
installStateUpdatedListener = installState -> {
if (installState.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdate();
}
};
mAppUpdateManager.registerListener(installStateUpdatedListener);
inAppUpdateType = AppUpdateType.IMMEDIATE; //1
inAppUpdate();
if(userFlag==1){
jtb.setChecked(true);
}
userL.setHint("Enter username");
sharedpreferences = getSharedPreferences(MyPREFERENCES, MODE_PRIVATE);
loginViewModel.getLoginFormState().observe(this, new Observer<LoginFormState>() {
@Override
public void onChanged(@Nullable LoginFormState loginFormState) {
if (loginFormState == null) {
return;
}
loginButton.setEnabled(loginFormState.isDataValid());
if (loginFormState.getUsernameError() != null) {
usernameEditText.setError(getString(loginFormState.getUsernameError()));
loginButton.startAnimation(AnimationUtils.loadAnimation(LoginActivity.this,R.anim.shake));
}
if (loginFormState.getPasswordError() != null) {
passwordEditText.setError(getString(loginFormState.getPasswordError()));
loginButton.startAnimation(AnimationUtils.loadAnimation(LoginActivity.this,R.anim.shake));
}
}
});
loginViewModel.getLoginResult().observe(this, new Observer<LoginResult>() {
@Override
public void onChanged(@Nullable LoginResult loginResult) {
if (loginResult == null) {
return;
}
if (loginResult.getError() != null) {
showLoginFailed(loginResult.getError());
}
if (loginResult.getSuccess() != null) {
updateUiWithUser(loginResult.getSuccess());
Intent i = new Intent(LoginActivity.this, user_dashboard.class);
startActivity(i);
}
setResult(Activity.RESULT_OK);
//Complete and destroy login activity once successful
}
});
TextWatcher afterTextChangedListener = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// ignore
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// ignore
}
@Override
public void afterTextChanged(Editable s) {
loginViewModel.loginDataChanged(usernameEditText.getText().toString(),
passwordEditText.getText().toString());
}
};
usernameEditText.addTextChangedListener(afterTextChangedListener);
passwordEditText.addTextChangedListener(afterTextChangedListener);
passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
loginViewModel.login(usernameEditText.getText().toString(),
passwordEditText.getText().toString());
}
return false;
}
});
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(userFlag==0) {
loginViewModel.login(usernameEditText.getText().toString(),
passwordEditText.getText().toString());
getStaffData();
}
else if(userFlag==1){
loginWorker();
}
}
});
jtb.setOnStateChangeListener(new JellyToggleButton.OnStateChangeListener() {
@Override
public void onStateChange(float process, State state, JellyToggleButton jtb) {
if (state.equals(State.LEFT)) {
userL.setHint("Enter username");
error = "Username cannot be empty";
userFlag = 0;
}
if (state.equals(State.RIGHT)) {
userL.setHint("Enter badge ID");
error = "Badge ID cannot be empty";
userFlag = 1;
}
}
});
}
@Override
protected void onDestroy() {
mAppUpdateManager.unregisterListener(installStateUpdatedListener);
finishAndRemoveTask();
super.onDestroy();
}
@Override
protected void onResume() {
try {
mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() ==
UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
try {
mAppUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
inAppUpdateType,
this,
RC_APP_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
//For flexible update
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdate();
}
});
} catch (Exception e) {
e.printStackTrace();
}
super.onResume();
}
@Override //For flexible update
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_APP_UPDATE) {
//when user clicks update button
if (resultCode == RESULT_OK) {
Toast.makeText(LoginActivity.this, "App download starts...", Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_CANCELED) {
//if you want to request the update again just call checkUpdate()
Toast.makeText(LoginActivity.this, "App download canceled.", Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_IN_APP_UPDATE_FAILED) {
Toast.makeText(LoginActivity.this, "App download failed.", Toast.LENGTH_LONG).show();
}
}
}
private void updateUiWithUser(LoggedInUserView model) {
String welcome = getString(R.string.welcome);
// TODO : initiate successful logged in experience
Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show();
}
private void showLoginFailed(@StringRes Integer errorString) {
Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
}
private void getStaffData() {
String username = usernameEditText.getText().toString();
APIInterface apiInterface3 = APIClient.getClient().create(APIInterface.class);
Call<loginList> call3 = apiInterface3.staffData(username);
call3.enqueue(new Callback<loginList>() {
@Override
public void onResponse(Call<loginList> call, Response<loginList> response) {
loginList list = response.body();
if (list!=null && list.getStatusCode()==1) { //response received.
if(list.getStaffList().size()>0){
Log.d("check-in", list.getStatusCode() + " " + list.getStaffList().get(0).getName());
Toast.makeText(LoginActivity.this,"Logged in",Toast.LENGTH_SHORT).show();
final String name = list.getStaffList().get(0).getName();
final String badge = list.getStaffList().get(0).getBadge();
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Name,name);
editor.putString(User,badge);
editor.putInt(con,1);
editor.apply();
}
else if(list.getStaffList().size()==0){
}
}
}
@Override
public void onFailure(Call<loginList> call, Throwable t) {
Log.d("fail",t.toString());
}
});
}
private void loginWorker(){
String username = usernameEditText.getText().toString();
String password = passwordEditText.getText().toString();
APIInterface apiInterface3 = APIClient.getClient().create(APIInterface.class);
Call<loginList> call3 = apiInterface3.loginWorker(username,password);
call3.enqueue(new Callback<loginList>() {
@Override
public void onResponse(Call<loginList> call, Response<loginList> response) {
loginList list = response.body();
Log.d("response", response.body().toString());
if (list!=null && list.getStatusCode()==1) { //response received.
if(list.getLoginList().size()>0){
Log.d("check-in", list.getStatusCode() + " " + list.getLoginList().get(0).getName());
Toast.makeText(LoginActivity.this,"Logged in",Toast.LENGTH_SHORT).show();
List<login> item = response.body().getLoginList();
final String name = list.getLoginList().get(0).getName();
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Name,name);
editor.putInt(con,1);
editor.apply();
}
String welcome = getString(R.string.welcome);
Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_SHORT).show();
Intent i = new Intent(LoginActivity.this, user_dashboard.class);
startActivity(i);
}
else
Toast.makeText(LoginActivity.this, "wrong ID or password",Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<loginList> call, Throwable t) {
Log.d("fail",t.toString());
}
});
editor = sharedpreferences.edit();
editor.putString(User, username);
editor.commit();
}
@Override
public void onBackPressed() {
new MaterialAlertDialogBuilder(LoginActivity.this,R.style.MyDialogTheme)
.setTitle("Exit")
.setMessage("Confirm to exit?")
.setBackground(getDrawable(R.drawable.alert_dialog))
// Specifying a listener allows you to take an action before dismissing the dialog.
// The dialog is automatically dismissed when a dialog button is clicked.
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Continue with delete
finishAffinity();
}
})
.setNegativeButton(android.R.string.no, null)
.show();
}
private void inAppUpdate() {
try {
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
@Override
public void onSuccess(AppUpdateInfo appUpdateInfo) {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
// For a flexible update, use AppUpdateType.FLEXIBLE
&& appUpdateInfo.isUpdateTypeAllowed(inAppUpdateType)) {
// Request the update.
try {
mAppUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// Or 'AppUpdateType.FLEXIBLE' for flexible updates.
inAppUpdateType,
// The current activity making the update request.
LoginActivity.this,
// Include a request code to later monitor this update request.
RC_APP_UPDATE);
} catch (IntentSender.SendIntentException ignored) {
}
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void popupSnackbarForCompleteUpdate() {
try {
Snackbar snackbar =
Snackbar.make(
findViewById(R.id.coordinatorL),
"An update has just been downloaded.\nRestart to update",
Snackbar.LENGTH_INDEFINITE);
snackbar.setAction("INSTALL", view -> {
if (mAppUpdateManager != null){
mAppUpdateManager.completeUpdate();
}
});
snackbar.setActionTextColor(getResources().getColor(R.color.orange));
snackbar.show();
} catch (Resources.NotFoundException e) {
e.printStackTrace();
}
}
}

The image I borrowed from google, on the left image as can be seen there is a cross button on top right user can click to close the update process
The most important point I will emphasize is that you should not force users to update the app until it is absolutely necessary (like some security issues etc). Forcing updates to users is considered a very bad user experience.
To the question you asked, you have the answer in your question itself. If you check the code you have something like this in your onActivityResult method-
if (requestCode == RC_APP_UPDATE) {
//when user clicks update button
if (resultCode == RESULT_OK) {
Toast.makeText(LoginActivity.this, "App download starts...", Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_CANCELED) {
//if you want to request the update again just call checkUpdate()
Toast.makeText(LoginActivity.this, "App download canceled.", Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_IN_APP_UPDATE_FAILED) {
Toast.makeText(LoginActivity.this, "App download failed.", Toast.LENGTH_LONG).show();
}
}
In case when the user cancels resultCode == RESULT_CANCELED or the update fails resultCode == RESULT_IN_APP_UPDATE_FAILED, you can take whatever action you want. You can finish the activity or whatever is suitable in your situation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With