Problem:
I'm encountering an unusual issue where the onCreate() method of my app's launcher activity (MainActivity) is being called twice, but only when I launch the app for the first time after installing it(from a Google Play Internal App Sharing link or installing signed apk file on device). This behavior results in multiple instances of MainActivity being created simultaneously.
Description:
I've thoroughly reviewed my code and can confirm that there are no accidental calls to startActivity or any explicit code that should cause onCreate() to be called more than once. I checked this but those bugs seems to be obsolete
Observations:
- This issue occurs only when the app is launched for the first time after installing.
- Subsequent launches of the app do not exhibit this behavior; onCreate() is called only once as expected.
- The problem seems to be related to the initial launch of the app after installation.
- I am testing in an Android 13 version device
Code:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xyz">
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Required to maintain app compatibility. -->
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<application
android:name=".framework.presentation.BaseApplication"
android:allowBackup="false"
android:fullBackupOnly="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="${enableCrashReporting}" />
<activity
android:name="xyz.framework.presentation.MainActivity"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="xyz.framework.presentation.main.RecipeActivity"
android:windowSoftInputMode="adjustResize"
android:exported="true" />
<activity
android:name="com.facebook.FacebookActivity"
android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:theme="@style/com_facebook_activity_theme"
android:exported="false" />
<activity
android:name="com.facebook.CustomTabMainActivity"
android:exported="false" />
<activity
android:name="androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity"
android:theme="@android:style/Theme"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
android:theme="@android:style/Theme"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyFloatingActivity"
android:theme="@android:style/Theme.Dialog"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>
BaseApplication
@FlowPreview
@ExperimentalCoroutinesApi
@HiltAndroidApp
open class BaseApplication : Application()
MainActivity
@AndroidEntryPoint
class MainActivity : AppCompatActivity(), UIController {
@Inject
lateinit var editor: SharedPreferences.Editor
@Inject
lateinit var sharedPreferences: SharedPreferences
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val viewModel: SplashViewModel by viewModels {
viewModelFactory
}
private val signInLauncher = registerForActivityResult(
FirebaseAuthUIActivityResultContract()
) { res ->
this.onSignInResult(res)
}
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Toast.makeText(this, "oncreate called", Toast.LENGTH_SHORT).show()
setContent {
SplashProvider()
}
viewModel.hasSyncBeenExecuted()
.observe(this) { hasSyncBeenExecuted ->
if (hasSyncBeenExecuted) {
startActivity(Intent(this, RecipeActivity::class.java))
// Use lifecycleScope for the coroutine
lifecycleScope.launch {
delay(2000) // Adjust the delay time as needed
finish()
}
}
}
if (sharedPreferences?.getString(
PreferenceKeys.USER_UID,
null
) != null && FirebaseAuth.getInstance().currentUser != null
) {
viewModel.syncCacheWithNetwork()
} else {
createSignInIntent()
}
}
private fun createSignInIntent() {
// [START auth_fui_create_intent]
// Choose authentication providers
val providers = arrayListOf(
AuthUI.IdpConfig.EmailBuilder().build(),
AuthUI.IdpConfig.GoogleBuilder().build()
)
Toast.makeText(this, "createSignInIntent called", Toast.LENGTH_SHORT).show()
// Create and launch sign-in intent
val signInIntent = AuthUI.getInstance()
.createSignInIntentBuilder()
.setAvailableProviders(providers)
.setTheme(R.style.LoginTheme)
.setLogo(R.drawable.handshake) // Set logo drawable
.build()
signInLauncher.launch(signInIntent)
// [END auth_fui_create_intent]
}
override fun hideSoftKeyboard() {
if (currentFocus != null) {
val inputMethodManager = getSystemService(
Context.INPUT_METHOD_SERVICE
) as InputMethodManager
inputMethodManager
.hideSoftInputFromWindow(currentFocus!!.windowToken, 0)
}
}
private fun onSignInResult(result: FirebaseAuthUIAuthenticationResult) {
val response = result.idpResponse
if (result.resultCode == AppCompatActivity.RESULT_OK) {
// Successfully signed in
val user = FirebaseAuth.getInstance().currentUser
// ...
editor?.putString(PreferenceKeys.USER_UID, user?.uid)
editor?.apply()
viewModel.syncCacheWithNetwork()
} else if (response != null) {
Toast.makeText(
this,
response.error?.errorCode.toString().plus(response.error?.message),
Toast.LENGTH_LONG
).show()
} else {
finish()
}
}
}
SplashProvider
@Composable
fun SplashProvider(
) {
val images = listOf(R.drawable.splash1, R.drawable.splash2, R.drawable.splash3)
val imageIndex = Random.nextInt(images.size)
Splash(images[imageIndex])
}
@Composable
fun Splash(img: Int) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Image(
contentDescription = "Recipe",
painter = painterResource(img),
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
Column(
modifier = Modifier
.wrapContentSize()
.clip(CircleShape)
.background(Color.Black.copy(alpha = 0.6f))
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(
modifier = Modifier.size(50.dp),
strokeWidth = 4.dp,
color = Color.White
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Loading...",
color = Color.White,
fontSize = 16.sp
)
}
}
}
The launch mode of your
MainActivityis not specified in yourAndroidManifest.xml. The default launch mode is "standard", which allows any number of instances to be created. You might want to try setting it to "singleTop", to make sure a new instance is not created if it is already at the top of the stack:For testing:
Also, in your
MainActivity, you launchRecipeActivityand then, after a delay of 2000 milliseconds, you callfinish()onMainActivity. That could potentially allow forMainActivityto be recreated if the system or user triggers a recreate (like a rotation or navigating back) within that time frame.Try calling
finish()immediately after starting theRecipeActivity, or better yet, use theFLAG_ACTIVITY_NO_HISTORYflag to ensureMainActivityis not kept in the activity stack:To help you debug this, log every call to
onCreate()with a timestamp and other relevant data, and try to trace back why it is being called twice: