Android onCreate is called twice after backing out - android

I have a simple MainActivity and if the app is completely killed it looks like onCreate() is called once. If however I back out of the app so it still appears in the background, when I re-open it I get every log message twice. The weirdest part is if I generate a random number it is always the same in the 2 log messages.
I've tried adding android:LaunchMode="singleTop" (also singleInstance singleTask) in the activity and application tags of the Manifest.
class MainActivity : AppCompatActivity() {
private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val view = binding.root
setContentView(view)
setupViews()
val data: Uri? = intent?.data
DataHolder.getInstance().setItem(data)
Timber.plant(Timber.DebugTree())
setupInjection()
Timber.d("review nanoTime = ${System.nanoTime()}")
Timber.d("review savedInstance = $savedInstanceState")
Timber.d("review random = ${Random.nextInt()}")
}
override fun onPause() {
Timber.d("review onPause()")
super.onPause()
}
override fun onStop() {
Timber.d("review onStop()")
super.onStop()
}
override fun onDestroy() {
Timber.d("review onDestroy()")
super.onDestroy()
finish()
}
override fun onStart() {
Timber.d("review onStart()")
super.onStart()
}
override fun onRestart() {
Timber.d("review onRestart()")
super.onRestart()
}
override fun onResume() {
Timber.d("review onResume()")
super.onResume()
}
private fun setupInjection() {
val appInjector = InjectorImpl(
firebaseAuth = FirebaseAuth.getInstance()
)
Injector.initialize(appInjector)
}
private fun setupViews() = binding.apply {
val navController = findNavController(R.id.nav_host_fragment)
navView.setupWithNavController(navController)
navView.setOnItemSelectedListener { item ->
when (item.itemId){
R.id.navigation_item_calculator -> {
navController.navigate(BuilderFragmentDirections.actionBuilderToCalculator())
}
R.id.navigation_item_builder -> {
navController.navigate(CalculatorFragmentDirections.actionCalculatorToBuilder())
}
}
true
}
navView.setOnItemReselectedListener { }
}
}
Here is a table of the log trace I get when I run the app on my phone from Android studio. Since the random numbers are the same I feel like this is actually a Logging bug in Android studio and the app isn't actually opened twice.

Realized my problem was with my logging library I used.
Timber was planting a new tree but wasn't uprooting old ones from being backed out so there were 2 instances of them. I fixed by putting a Timber.uprootAll() just before Timber.plant(Timber.DebugTree())

Related

Check if first time using the app during launch

I'm trying to implement a first time using screen (like any other app when you have to fill some options before using the app for the first time).
I can't go to another Jetpack compose on an main activity on-create state because it check that every recomposition, and take me to the navigation path (I'd like to check the datastore entry once during launch), this what I already try, not seem to be working:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
val onBoardingStatus = dataStoreManager.onBoard.first()
setContent {
val navController = rememberNavController()
OnBoardingNavHost(navController)
navController.navigate(if (onBoardingStatus) "on_boarding" else "main") {
launchSingleTop = true
popUpTo(0)
}
}
}
}
it is possible to check that only once (in application class for example and not in oncreate?)
please advice,
thanks in advance
You have to use LaunchedEffect for this, you can do something like this
enum class OnboardState {
Loading,
NoOnboarded,
Onboarded,
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var onboardingState by remember {
mutableStateOf(OnboardState.Loading)
}
LaunchedEffect(Unit) {
onboardingState = getOnboardingState()
}
when (onboardingState) {
OnboardState.Loading -> showSpinner()
OnboardState.NoOnboarded -> LaunchedEffect(onboardingState) {
navigateToOnboarding()
}
OnboardState.Onboarded -> showContent()
}
}
}

Cant add fragment inside of lifecycleScope coroutine

I am using lifecycleScope.launch in my activity's onCreate to collect a flow but I also am trying to attach a fragment inside the scope but I just get a black screen when trying to do this like it never gets attached.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
lifecycleScope.launch {
_viewModel.listenForScheduleChanges().flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect {
}
_viewModel.loadConfig() // suspend method that loads information
_webFragment = WebFragment().apply {
arguments = Bundle().apply {
putParcelable("config", _viewModel.config)
}
}
supportFragmentManager.beginTransaction()
.replace(R.id.contentPanel, _webFragment!!)
.commit()
}
}
If I dont use lifecycleScope and implement CoroutineScope in my activity with that coroutine scope the fragment attaches fine
class MainActivity : AppCompatActivity(), CoroutineScope{
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
lifecycleScope.launch {
_viewModel.listenForScheduleChanges().flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect {
}
}
launch {
_viewModel.loadConfig() // suspend method that loads information
_webFragment = WebFragment().apply {
arguments = Bundle().apply {
putParcelable("config", _viewModel.config)
}
}
supportFragmentManager.beginTransaction()
.replace(R.id.contentPanel, _webFragment!!)
.commit()
}
}
}
I dont understand why, both appear to be using the same context with the Dispatcher as Main.
Can someone provide insight here?
Separate the code that collects data from flow to another coroutine scope
your problem will be solved:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
_viewModel.listenForScheduleChanges().collect {
}
}
}
lifecycleScope.launch {
_viewModel.loadConfig() // suspend method that loads information
_webFragment = WebFragment().apply {
arguments = Bundle().apply {
putParcelable("config", _viewModel.config)
}
}
supportFragmentManager.beginTransaction()
.replace(R.id.contentPanel, _webFragment!!)
.commit()
}
}
the issue is that when you collect a flow in this way it blocks the whole scope and other codes in the scope are not running.

onSharedPreferenceChanged doesn't proc, Kotlin/Android

So I have my settings activity set up with my < PreferenceScreen > for choosing the app settings.
What I want to do is have my changes do immediate effect, and not on app restart.
Naturally I wanted to use OnSharedPreferenceChangeListener , but if I try to put a Log in onSharedPreferenceChanged, it never procs on changed selection. My plan was to recreate() on preference changed, so my preference apply function would proc on onCreate() with that recreate().
The problem is, as previously stated that onSharedPreferenceChanged never procs.
I tried registering the listener as it was stated somewhere, but it didn't help.
Can anyone help me?
class SettingsActivity : AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
SharedPreferences.OnSharedPreferenceChangeListener {
val Tag = "My Activity:"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val prefMen = SharedPreferencesManager(this)
prefMen.loadTheme()
setContentView(R.layout.activity_settings)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.content_preference, MainPreference()).commit()
} else {
title = savedInstanceState.getCharSequence(TAG_TITLE)
}
supportFragmentManager.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0) {
setTitle("Settings")
}
}
setUpToolbar()
}
override fun onStart() {
super.onStart()
getPreferences(MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(this)
}
override fun onDestroy() {
super.onDestroy()
getPreferences(MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
super.onSaveInstanceState(outState, outPersistentState)
outState.putCharSequence(TAG_TITLE, title)
}
override fun onSupportNavigateUp(): Boolean {
if (supportFragmentManager.popBackStackImmediate()) {
return true
}
return super.onSupportNavigateUp()
}
private fun setUpToolbar() {
supportActionBar?.setTitle("Settings")
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
}
class MainPreference : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
}
override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat?,
pref: Preference?
): Boolean {
val args = pref?.extras
val fragment = pref?.fragment?.let {
supportFragmentManager.fragmentFactory.instantiate(
classLoader,
it
).apply {
arguments = args
setTargetFragment(caller, 0)
}
}
fragment?.let {
supportFragmentManager.beginTransaction().replace(R.id.content_preference, it)
.addToBackStack(null).commit()
}
title = pref?.title
return true
}
companion object {
private val TAG_TITLE = "PREFERENCE_ACTIVITY"
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
recreate()
}
}
I see two mistakes. The first is that you never register your listener. You have called unregisterOnSharedPreferenceChangeListener() in your onStart() instead of registerOnSharedPreferenceChangeListener().
Secondly, you are not listening to the same SharedPreferences as the settings Fragment is using. PreferenceFragmentCompat by default uses the default shared preferences of the whole application. But you are listening to preferences retrieved with Activity.getPreferences(), which are not the default preferences, but rather a SharedPreferences instance that is named after the Activity.
So you should switch to using default shared preferences, because the private shared preferences of the Activity will not be as easy to get a reference to in other activities.
I would also register in onCreate() instead of onStart() so you aren't registering multiple times. I don't know if that really matters, but the documentation doesn't specify what happens when you repeatedly register the same listener. Maybe it would fire the callback repeatedly, which could end up wasting your time hunting the bug later.
override fun onCreate() {
// ...
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this)
}
override fun onDestroy() {
super.onDestroy()
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this)
}
It is possible to specify the SharedPreferences that will be used by PreferenceFragmentCompat, but the default preferences are the easiest to use for settings that you want to access from across your application.

MediaSession callback is never called

I've created a simple app with one activity and I create a MediaSession in its onCreate method
However when I run the application and use external media buttons the callback is never called. Any ideas what I might be missing?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
this.mediaSession = MediaSessionCompat(this, "TAG")
this.mediaSession?.setCallback(object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
return super.onMediaButtonEvent(mediaButtonIntent)
}
override fun onPlay() {
super.onPlay()
}
})
val builder = PlaybackStateCompat.Builder()
builder.setActions(PlaybackStateCompat.ACTION_PLAY)
builder.setState(PlaybackStateCompat.STATE_STOPPED, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0.0f)
this.mediaSession?.setPlaybackState(builder.build())
this.mediaSession?.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
this.mediaSession?.isActive = true
}
You need to implement BroadcastReceiver. One example here: https://github.com/tutsplus/background-audio-in-android-with-mediasessioncompat/blob/master/app/src/main/java/com/tutsplus/backgroundaudio/BackgroundAudioService.java

Two consecutive Toast messages won't show when they are called exactly after each other on Android Oreo

The following code properly shows two consecutive messages on devices running Android version lower than 27, but on Android version 27, it just shows the message of the first Toast.
What has changed in Android 27 that makes it behave differently?
Should two consecutive Toast messages have gap longer than specific time?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Toast.makeText(this, "The onCreate method is called", Toast.LENGTH_SHORT).show()
}
override fun onResume() {
super.onResume()
Toast.makeText(this, "The onResume method is called", Toast.LENGTH_SHORT).show()
}
You need to write a small program that tracks State
I ran with API 26 and API 27 and this line of code is only called when you navigate back from another Activity
Toast.makeText(this, "The onResume method is called", Toast.LENGTH_SHORT).show()
Enjoy
OK Here is how you write a small program that tracks STATE
This is the Activity that Gives you output set your logcat to debug note the TAG
open class BaseActivity : AppCompatActivity() {
val TAG = "LifeCycle"
override fun onCreate(savedInstanceState: Bundle?) {
Log.d(TAG, "${javaClass.simpleName} OnCreate")
println("========================== OnCreate")
super.onCreate(savedInstanceState)
}
override fun onStart() {
Log.d(TAG, "${javaClass.simpleName} OnStart")
println("========================== OnStart")
super.onStart()
}
override fun onResume() {
Log.d(TAG, "${javaClass.simpleName} OnResume")
println("========================== OnResume")
super.onResume()
}
override fun onRestart() {
Log.d(TAG, "${javaClass.simpleName} OnRestart")
println("========================== OnRestart")
super.onRestart()
}
override fun onPause() {
Log.d(TAG, "${javaClass.simpleName} OnPause")
println("========================== OnPause")
super.onPause()
}
override fun onStop() {
Log.d(TAG, "${javaClass.simpleName} OnStop")
println("========================== OnStop")
super.onStop()
}
override fun onDestroy() {
Log.d(TAG, "${javaClass.simpleName} OnDestroy")
println("========================== OnDestroy")
super.onDestroy()
}
}
Then as you Navigate from one Activity Class to another you will get output
Provided you use this heading in the Class
class WelcomeActivity : BaseActivity() {

Categories

Resources