Activity screen still here after finish() - android

I have a particular case where I need to ask something to the user when he starts a printer service.
So from the onStartPrinterDiscovery (so a service), I start an activity to display the dialog and when the action is done, I send a new intent which calls finish() nd so I see that onDestoy() is called.
Unfortunately when I hit the apps history button, I still see my activity's screen behind:
Could you tell me why and how to fix it please?
androidManifest.xml:
<activity
android:name=".DialogActivity"
android:noHistory="true"
android:theme="#android:style/Theme.Dialog"
android:launchMode="singleTop">
</activity>
DialogActivity:
class DialogActivity : Activity() {
var activity:Activity = this
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(R.layout.floatingactivity)
setFinishOnTouchOutside(false)
onNewIntent(intent)
}
override fun onDestroy() {
super.onDestroy()
isDisplayed = false
}
override fun onNewIntent(intent: Intent) {
val extras = intent.extras
val action = extras.getString("action")
when (action) {
"showDialog" -> {
if (!isDisplayed) {
tvMessage.text = getString(R.string.ask_for_action)
isDisplayed = true
}
}
"showErrorDialog" -> {
if (!isDisplayed) {
tvMessage.text = getString(R.string.error_action)
isDisplayed = true
}
}
"dismissDialog" -> { activity.finish() }
else -> {}
}
if (isDisplayed) {
btCancel.setOnClickListener {
activity.finish()
}
}
}
companion object {
var isDisplayed = false
}
}
EDIT I add how I currently create my Intent because of one answer which could be a solution:
val intent = Intent(applicationContext, DialogActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
intent.putExtra("action","showDialog")
startActivity(intent)

Add the android:excludeFromRecents to your activity attributes, like so:
<activity
android:name=".DialogActivity"
android:theme="#android:style/Theme.Dialog"
android:excludeFromRecents="true"
android:launchMode="singleTop">
</activity>
This is from the docs of the android:excludeFromRecents:
Whether or not the task initiated by this activity should be excluded from the list of recently used applications, the overview screen. That is, when this activity is the root activity of a new task, this attribute determines whether the task should not appear in the list of recent apps. Set "true" if the task should be excluded from the list; set "false" if it should be included. The default value is "false".
The android:noHistory="true" is for different purposes. This is from the docs of the android:noHistory:
A value of "true" means that the activity will not leave a historical trace. It will not remain in the activity stack for the task, so the user will not be able to return to it. In this case, onActivityResult() is never called if you start another activity for a result from this activity.

Try this:-
Intent i = new Intent(this,YourFirstActivity.Class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);
finish();
Or
use finishAffinity() suitable for >= API 16.

Related

registerForActivityResult not working when second activity orientation changes

I am calling activity B from activity A using the ActivityResultLauncher and setting the result from activity B when the task is done. This works perfectly if orientation is not changed. The problem is when I change orientation from activity B and then setting the result, then registerForActivityResult of activity A is not called. Could someone let me know, what could be the issue?
Note: I do not face this issue if I am using startActivityForResult and onActivityResult. I changed this to ActivityResultLauncher as startActivityForResult became deprecated.
activity A sample code:
private lateinit var launcher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout)
setLauncherResult()
}
private fun setLauncherResult() {
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
//Do operations here
}
}
//On button click starting activity B using launcher
val intent = Intent(activityA, activityB)
launcher.launch(intent)
}
activity B sample code:
//setting result
val intent = Intent()
//set intent extras
setResult(Activity.RESULT_OK, intent)
finish()
You can save the value in onSaveInstanceState() and use setResult in onRestoreInstanceState().
Try moving the registerForActivityResult assignment of launcher to onCreateView() (override it if you haven't already). Putting it here, I'm not getting the "is attempting to register while current state is STARTED" RuntimeException in OnCreateView() as it would with onResume() or later. The advantage of OnCreateView() over OnCreate() seems to be that the launcher variable will get re-set on theme day/night changes as well as just orientation changes.
Also see the Configuration change & Process death section here for more discussion.
I know the official dox say it's safe to set it before anything else in the class, but theme changes broke .launch(), and this location appears to work for me.
Everytime orientation changes it calls onCreate and it resets your views & Other variables like launcher are re-assigned. So you can follow the example shown in official document here: https://developer.android.com/training/basics/intents/result#launch
And pull your launcher assignment out of onCreate as following (This worked for me):
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
//do your stuff here
binding.tv.text = result.data?.getStringExtra("text")
}
}
companion object {
var number : Int = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tv.setOnClickListener {
val intent = Intent(this, ActivityB::class.java)
startForResult.launch(intent)
}
}
}
If you don't want that onCreate should be called again when orientation changes. Then in your Manifest you can add configChanges:
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">

How to refresh activity programmatically with shared preferences used in kotlin?

I'm still new to Kotlin and Android and developing a project that uses shared preferences.
I have multiple Activities and a user must be logged in to use all functions of the Main Activity.
The Main Acitivity has a menu drawer with few menu items. The first one is redirecting user to Login Activity. The rest of the menu items are hidden until user is logged in.
The process is as follows:
Start with Main Activity -> The application checks if the user is logged in -> If not you must log in -> Go to Login Activity -> Logging in -> Then go back to Main Activity.
However, the main activity does not refresh at all. I mean, I don't even know how to do it. I want my main activity to refresh when I return from login activity.
So far, it only works when I close the app and reopen it. The session is likely cached in Shared preferences.
I tried to start and finish Activites by clicking certain buttons (eg After clicking login button when user passed all credentials) but it didn't work.
The following code checks that the user is logged in when the main activity starts
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val menu: Menu = navView.menu
val target2: MenuItem = menu.findItem(R.id.miItem2)
val target3: MenuItem = menu.findItem(R.id.miItem3)
//region user session
try{
if(!SharedPrefManager.getInstance(this)!!.isLoggedIn()){
target2.isVisible = false
target3.isVisible = false
Toast.makeText(applicationContext, "nie zalogowany", Toast.LENGTH_LONG).show()
}
else{
target2.isVisible = true
target3.isVisible = true
Toast.makeText(applicationContext, "zalogowany", Toast.LENGTH_LONG).show()
}
}catch (e: NullPointerException){
}
//endregion
And there is function isLoggedIn():
fun isLoggedIn(): Boolean{
val sharedPreferences: SharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
if(sharedPreferences.getString(KEY_USER_EMAIL, null) != null){
return true
}
return false
}
This code below takes me to the Login Activity
val btnLogInActivity : Button = findViewById(R.id.logButton)
btnLogInActivity.setOnClickListener {
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
}
And this is the code in Login Activity (userLogin function is not important in this context):
buttonLogin.setOnClickListener {
userLogin()
newMainActivity()
}
private fun newMainActivity(){
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
Ps. If you need more code to fix the problem, let me know
You just have to add finish() in your code. Update your methods with the code below:
btnLogInActivity.setOnClickListener {
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
finish()
}
And after login:
private fun newMainActivity(){
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}

What can keep activity alive after finish()?

I have HomeScreen and LoginScr activity and I would like to move from HomeScreen to LoginScr activity. I do it in such way:
val thread = Thread(Runnable {
try {
val client = APICallRequests.client
client.dispatcher.executorService.shutdown()
client.connectionPool.evictAll()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
})
thread.start()
val intent = Intent(this, LoginScr::class.java)
startActivity(intent)
finishAfterTransition()
As you can see I try to close okHttp3 client, because maybe it keeps HomeScr activity alive. Also I use finishAfterTransition(). Then when I move from LoginScr to HomeScr I do it in such way:
private fun moveToNextScreen(){
val goToMain = Intent(this, HomeScreen::class.java)
startActivity(goToMain
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
)
finishAfterTransition()
overridePendingTransition(0, 0)
}
As I see I create two activity instances instead one. How I did it - added log to onCreate() fun. I totally don't understand why does it happen. Similar problem can be met after onBackPressed():
override fun onBackPressed() {
val startMain = Intent(Intent.ACTION_MAIN)
startMain.addCategory(Intent.CATEGORY_HOME)
startMain.flags = Intent.FLAG_ACTIVITY_NO_ANIMATION
startActivity(startMain)
finish()
super.onBackPressed()
}
I tried a lot of variants but my HomeScreen can stay alone. I also tried such ways at manifest:
android:launchMode="singleTask"
android:noHistory="true"
Maybe someone know how to solve this problem?
I think this answer can help you
https://stackoverflow.com/a/10862977/13221053
Further, if you want to keep your app active in background then you can use services for that purpose, here is the documentation
https://developer.android.com/guide/components/services

Kotlin overridden onBackPressed() function is never called when Action Bar back button is pressed

I've seen this question posed all over the place but I have not seen where anyone has been able to answer why the function doesn't get called.
I've run the app in debug mode with a break point set at the onBackPressed function and it completely ignores it. The compiler even recognizes and changes my red break point into a circle with a line through it.
I have a ContractSelectAdapter with a setOnClickListener passing a selected contract to ContractMenuActivity using putExtra(). Once in the ContractMenuActivity there is a button to open the activity below, UploadImageActivity and while in the activity I want a user to be able to hit the back button to reopen the ContractMenuActivity but within the onCreate function of ContractMenuActivity is a getStringExtra call that causing it to crash because there is no "extra". Which is why I'm trying to use the onBackPressed. In theory it should work perfectly.
I've tried putting a call to the onBackPressed function manually in the onCreate function of UploadImageActivity just to make sure it works and it does. It immediately opens ContractMenuActivity. So the function works it just never gets called for some reason. Extremely frustrating.
the onBackPressed function is at the very end of the UploadImageActivity class (first code excerpt)
Any help would be GREATLY appreciated.
class UploadImageActivity : AppCompatActivity() {
var prefs: Prefs? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_upload_image)
prefs = Prefs(this)
}
override fun onBackPressed()
{
super.onBackPressed()
val returnIntent = Intent(this, ContractMenuActivity::class.java)
returnIntent.putExtra(SELECTED_CONTRACT, prefs!!.rmsAppContractID)
startActivity(returnIntent)
}
}
class ContractMenuActivity : AppCompatActivity()
{
var selectedContract: String = ""
var prefs: Prefs? = null
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_contract_menu)
prefs = Prefs(this)
val btnUploadImage = findViewById<Button>(R.id.btnUploadImage)
selectedContract = intent.getStringExtra(SELECTED_CONTRACT)
prefs!!.rmsAppContractID = this.selectedContract
var contractSelectMsg = "Contract ${prefs!!.rmsAppContractID} selected"
val duration = Toast.LENGTH_SHORT
Toast.makeText(this, contractSelectMsg, duration).show()
btnUploadImage.setOnClickListener {this.openUploadImage()}
}
public fun openUploadImage()
{
val registered = prefs!!.rmsAppRegistered
var message: String
if(registered)
{
val intent = Intent(this, UploadImageActivity::class.java)
startActivity(intent);
}
else
{
message = "You must Pair this device before you can upload an image."
val intent = Intent(this, PostResponseMessageActivity::class.java).apply {
putExtra(POST_RESPONSE_MESSAGE, message)
}
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
}
Parent name in manifest:
<activity
android:name=".ContractMenuActivity"
android:label="#string/contractMenuLabel"
android:parentActivityName=".ContractSelectActivity">
</activity>
<activity
android:name=".UploadImageActivity"
android:label="#string/uploadImageLabel"
android:parentActivityName=".ContractMenuActivity">
</activity>
super.onBackPressed()
Will finish the current activity. If you want to do some action after user press back button, You must to past your code to top of super.onBackPressed().

Calling an Activity with FLAG_ACTIVITY_REORDER_TO_FRONT from a Service

I am trying to call an activity (activity A) from a service and what I want to happen is to check if there's already an instance of A on the stack, and if there is, to bring that to the top of the stack (and trigger onNewIntent() method), instead of always creating a new instance of A.
Wondering if this is possible. My activity uses the "singleTop" launchmode in the androidmanifest. The usual Intent.FLAG_ACTIVITY_NEW_TASK flag that is required to call an activity from outside an activity doesn't bring the already open activity A to the top of the stack, but always creates a new instance of A. Also it seems that when I use both flags (intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) the same thing happens (still 2 instances of the same activity). Is there a way to go about doing this, always keeping in mind that I am making the call from a service, and I don't want to pass the activity context to the service?
(P.S. I am using androidannotations, the call to open activity A occurs inside an #EBean, which itself is used in a Service. Is there an easy way to somehow pass that activity context to that #EBean?)
Since you want to use a non-activity context (e.g. applicationContext) to start the activity leading to use Intent.FLAG_ACTIVITY_NEW_TASK, you have only two choices to avoid creating a new instance of the target activity every time you're calling startActivity.
First, by specifying android:launchMode="singleInstance" in the manifest for the activity, you can force that only one instance of the activity should exist in a task that hosts only this instance. In this case, starting the activity brings it to the front if there exists in the host task, otherwise, a new task gets created containing the only instance of the activity. I think it's not the way we are looking for.
Second, by specifying android:launchMode="singleTask" in the manifest for the activity, we can achieve a better solution. In this case, the system creates a new task and adds the activity at the root of this task, if there exists no instance of the activity. Otherwise, the system brings the task containing the activity instance to the front then routes to the target activity, and calls onNewIntent.
Here is an example code of the second approach, examines 2 scenarios:
manifest.xml:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ActivityA" android:launchMode="singleTask" />
<activity android:name=".ActivityB" />
MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// First Scenario: ActivityA doesn't exist in back-stack
button1.setOnClickListener {
Intent(applicationContext, ActivityA::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}.let {
applicationContext.startActivity(it)
}
}
// Second Scenario: ActivityA exists in back-stack
button2.setOnClickListener {
startActivity(Intent(this, ActivityA::class.java))
// Start ActivityB after a while
Handler().postDelayed({
startActivity(Intent(this, ActivityB::class.java))
}, 1000)
}
}
}
ActivityA.kt
class ActivityA : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_a)
Toast.makeText(this, "onCreate on ActivityA", Toast.LENGTH_SHORT).show()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Toast.makeText(this, "onNewIntent on ActivityA", Toast.LENGTH_SHORT).show()
}
}
ActivityB.kt
class ActivityB : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_b)
button.setOnClickListener {
Intent(applicationContext, ActivityA::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}.let {
applicationContext.startActivity(it)
}
}
}
}
Result:
.......

Categories

Resources