Android MainActivity's onCreate() Called Twice on Initial Launch After Installing

568 Views Asked by At

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
            )
        }

    }
}
4

There are 4 best solutions below

1
On

In my case, I confirmed that this problem only occurs in Android 13 version and that there is a problem with the appcompat version.

androidx.appcompat:appcompat:1.6.1 
-> androidx.appcompat:appcompat:1.5.1

Changing the version solved the problem.

0
On

I had the same problem, and it seems to be a bug in Android. While this solution did help to some extent, it led to unexpected behavior where sometimes, the code inside onCreate() wasn't executed at all. So, I came up with an alternative workaround:

class SingleOnCreateExecution(context: Context, savedInstanceState: Bundle?, executeCode: () -> Unit) {

init {
    val preferences = context.getSharedPreferences("executeOnce", Context.MODE_PRIVATE)
    val launchCount = preferences.getInt("launchCount", 0)

    when (launchCount) {
        1 -> if (savedInstanceState == null) executeCode() // Second time run only if no saved state
        else -> executeCode() // Runs when launchCount is 0 or after the second time, regardless of saved state
    }

    preferences.edit().putInt("launchCount", launchCount + 1).apply()
}
}

and run it like this:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    SingleExecution(this, savedInstanceState) {
        Log.i("myTag", "onCreate")
        // ... your onCreate() code here
    }
}

This ensures that the check "savedInstanceState == null" is only made on the second launch. As a result, onCreate() runs normally most of the time, minimizing disruptions.

If you're already tracking the app's launchCount in your application, you can use your existing SharedPreferences.

0
On

Used this code - I hope your side working

val intent = Intent(this, EmpDashboardActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP.or(Intent.FLAG_ACTIVITY_SINGLE_TOP)
        intent.flags = Intent.FLAG_ACTIVITY_NO_HISTORY.or(Intent.FLAG_ACTIVITY_SINGLE_TOP)
        startActivity(intent) finish()

used in manifest file in splash activity

<activity
        android:name=".housescanify.survey.empSurveyUi.surveyWelcome.WelcomeSurveyActivity"
        android:configChanges="orientation|screenSize"
        android:exported="false"
        android:launchMode="standard"
        android:screenOrientation="portrait"
        tools:ignore="LockedOrientationActivity">
        <meta-data
            android:name="android.app.lib_name"
            android:value="" />
    </activity>
0
On

The launch mode of your MainActivity is not specified in your AndroidManifest.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:

<activity
    android:name="xyz.framework.presentation.MainActivity"
    android:launchMode="singleTop"
    android:windowSoftInputMode="adjustResize"
    android:exported="true">

Also, in your MainActivity, you launch RecipeActivity and then, after a delay of 2000 milliseconds, you call finish() on MainActivity. That could potentially allow for MainActivity to 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 the RecipeActivity, or better yet, use the FLAG_ACTIVITY_NO_HISTORY flag to ensure MainActivity is not kept in the activity stack:

Intent intent = new Intent(this, RecipeActivity.class.java);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(intent);
finish();

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:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("MainActivity", "onCreate called at " + System.currentTimeMillis());
    // Rest of the code
}