Kotlin: Load/initialize next activity in the background - android

I have an initialization activity in my app which displays the logo, then I show my next activity using
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // Call the parent class function
setContentView(R.layout.activity_launcher)
// This starts a new co-routine
// it is important to do it this way, in order to show the UI _before_
// all the initialization happens, otherwise launcher is pointless
GlobalScope.launch {
...
[initialization]
...
startActivity(ActivityTwo)
}
}
The transition takes about three seconds because of all the code that is running inside onCreate belonging to ActivityTwo. Is there a way to "create" the second activity behind the scenes, and then show it. I don't mind if the app stays on the initialization screen for those 3 seconds, but the white transition looks really ugly.

onCreate method is actually creating your activity. You must be doing some heavy computation if your activity is janking while rendering. If you are not satisfied with the transition between the two activities, then apply an animation between them.

Related

Fragment getting initialised before Android 12 Splash screen ends

I have a FragmentContainerView in my MainActivity which uses a nav graph.
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav" />
I want my fragments to wait till I get some data from an API before I hide my splash screen. I am using android 12 Splash Screen. This is how I am trying to accomplishing it(from documentation):
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()
setContentView(R.layout.activity_main)
//Set up an OnPreDrawListener to the root view.
val content: View = findViewById(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
// Check if the initial data is ready.
return if (mainActivityViewModel.isDataReady()) {
// The content is ready; start drawing.
Timber.tag("Splash").d("data ready")
content.viewTreeObserver.removeOnPreDrawListener(this)
true
} else {
Timber.tag("Splash").d("data not ready")
// The content is not ready; suspend.
false
}
}
}
)
The Splash screen is going away only after I get the data. But the issue is that my Fragments callbacks like onViewCreated gets invoked even before my data is ready. This is causing issues because I rely on data that I fetch during splash screen to do some tasks.
How can I make sure, my fragments won't get initialised before my Splash screen goes away??
The Splash screen is going away only after I get the data. But the issue is that my Fragments callbacks like onViewCreated gets invoked even before my data is ready.
Activity and fragment lifecycle callbacks are only triggered by the system on the appropriate times. They can't be triggered by developers; there is no control on that. So, you can't stop onCreateView, onViewCreated,.. from taking place. i.e. you can't stop activity/fragment from initialization; otherwise you could have ANRs.
More in detail
Initializations of activities/fragments shouldn't be seized until some background task finishes; this literally will cause ANRs.
While you're seizing the drawing of the activity's root layout using addOnPreDrawListener; this doesn't mean that the activity's initialization (i.e. onCreate()) get seized, because both are working asynchronously, and the onCreate(), onStart(), & onResume() will get return normally.
The same is true for fragment's callbacks where the fragment's initialization (i.e. onCreateView(), onViewCreated...) is coupled with the activity's onCreate().
Now, as the initialization of the start destination fragment relies on the API data; then this particular fragment shouldn't be the start destination.
So, to fix this you need to create a some splash screen fragment as the start destination that doesn't rely on the API data.
Depending on the API incoming data, you can decide a navigation to the original fragment through the navController:
return if (mainActivityViewModel.isDataReady()) {
// Do the transaction to the original fragment through the navController
// The content is ready; start drawing.
Timber.tag("Splash").d("data ready")
content.viewTreeObserver.removeOnPreDrawListener(this)
true
} else {
// Not transaction is needed, keep the splash screen fragment
Timber.tag("Splash").d("data not ready")
// The content is not ready; suspend.
false
}
}
}

Android 12 Splash Screen API inconsistent behavior

I am implementing the new Splash Screen API but encountered an inconsistent behavior on it. Sometimes the screen with app icon shows and sometimes it doesn't. There is also a long white screen on the beginning which is visibly annoying (The attached image was just speed up 3x since I cannot upload image file higher than 2mb here, but the white screen was clearly visible for a couple of seconds and Splash API seems to cause frame skip from Choreographer log).
Samsung J1 Android L
class LauncherActivity : AppCompatActivity() {
private var keepSplash = true
private lateinit var splashScreen: SplashScreen
override fun onCreate(savedInstanceState: Bundle?) {
splashScreen = installSplashScreen().apply {
// Behaves like observable, used to check if splash screen should be keep or not
setKeepOnScreenCondition {
keepSplash
}
setOnExitAnimationListener { sp ->
sp.remove() // Remove splash screen
}
}
super.onCreate(savedInstanceState)
}
fun fetchData() {
//Fetching network data...
keepSplash = false
}
Showing the AlertDialog seems not working unless I minimize the app and reopen it with setKeepOnScreenCondition. It seems to block the UI thread, is there other way to retain the splash but not a blocking UI one? Currently we need to show an AlertDialog if something went wrong but at the same time the splash screen will be retain until the dialog is dismiss.
I solved the issue, first if you want to keep the splash icon screen on user screen you need to use both setKeepOnScreenCondition and setOnExitAnimationListener
splashScreen.apply {
// Behaves like observable, used to check if splash screen should be keep or not
setKeepOnScreenCondition {
keepSplash // True to keep the screen, False to remove it
}
setOnExitAnimationListener { splashScreenViewProvider ->
// Do nothing so the splash screen will remain visible
}
}
Just remember that setKeepOnScreenCondition can be a UI blocking thread so if ever you are fetching some data during splash screen and showed an error message via dialog, Toast, or SnackBar it wont work. You need to set setKeepOnScreenCondition to false first.
The role of empty setOnExitAnimationListener here is not to remove the splash screen even after setting a false condition on setKeepOnScreenCondition.
UPDATED
It is probably best to just use and empty setOnExitAnimationListener if you want to control and extend the Splash Screen. Then save its splashScreenViewProvider in a variable and use it later to control or dismiss the screen by calling remove(). Documentation is here.

Android Kotlin - How execute an animation only when app opens?

I would like that animation on MainActivity to be exhibited only when the user opens the app, but remains static if the user comes from another Activity/Fragment (e.g. by using BottomNavigationMenu), so as not to pollute the window too much.
I think it can be solved by using onCreate, onStart, onResume but I am unable to set it properly (still learning).
The only answer I found is here: https://www.tutorialspoint.com/how-to-launch-activity-only-once-when-android-app-is-opened-for-the-first-time
but it is not what I want since I still would like the animation to be exhibited every time the app opens.
Thank you in advance.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// execute animation here??
}
override fun onStart() {
super.onStart()
setContentView(R.layout.activity_main)
// logic part (buttons) is executed here??
}
Once the Main activity is loaded then you have to finish() from other Activity/Fragment then onResume() override method Main Activity will be called. Do not start Main activity every time if the user comes from another Activity/Fragment.
For fragments:
activity.finish()
For Activity:
finish()
Rather then:
Intent startIntent = new Intent(context, MainActivity.class);
startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(startIntent);

How can I use button in one activity to do something in second activity?

I got a question. I have two activities in my app. In first one, when I click the button, I need something to happen in the second one. How can I do it? If that button would be in the second activity I would just do it by:
button.setOnClickListener {}
But how can I do it when button is in the other activity? It's worth adding that code, that tells what should happen, must be in that second activity, just like it was in that "setOnClickListener". Sorry, I'm starting with Android development.
You could communicate between two activities via broadcast or intent.
But it make logic more complex.
So I suggest use two fragments instead two activities.
If you use two fragment in one activity, you can easy communicate between two fragments.
You can look more detailed information about fragment from this URL.
https://developer.android.com/guide/fragments
To achieve the intended flow you may try the below approach,
Start activity 2 on button click from activity 1.
On activity 2 place your code in onCreate so, once activity 2 loads up your code will fire up.
Activity 1:
button.setOnClickListener{
val intent = Intent(this, second::class.java)
startActivity(intent)
}
Activity 2:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CallDefinedFunctionHere();
}
You cannot be sure that both activities are present at the same moment since the system might destroy inactive one therefore you cannot trigger any code from activity A inside activity B.
What you can do you can start activity B with an intent and some parameters describing what should happen inside activity B.
or
You can communicate by writing something down to a persistent storage (like SharedPreferences) and then when the other activity is resumed (active again) reading it, reacting to it and then removing it from the storage (to make sure you do not handle it twice).
You can pass data in the intent that opens the second activity.
// In first activity:
buttonX.setOnClickListner {
val intent = Intent(MySecondActivity::class.java).apply {
putExtra("wasFromButtonX", true)
}
startActivity(intent)
}
// In second activity:
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val wasLaunchedFromButtonX = intent.getBooleanExtra("wasFromButtonX", false)
// Above line uses false as default, so it will only be true if you explicitly
// put the extra in the Intent that started this Activity.
if (wasLaunchedFromButtonX) {
// do alternate setup here
}
}
How to pass information between Activities is explained in the introductory documentation here.
Create a function in class where 2nd activity are defined like this.
public void refresh(){}
Now Call that in your 1st activity where you want to call 1st after any action.
button.setOnClickListener {((MainActivity) Objects.requireNonNull(getActivity())).refresh();}

Showing DialogFragment throws exception when activity is not on screen

I have within an Activity, called CounterActivity, a counter that counts let's say from 100 down to 0 with a second interval. A DialogFrament is shown when the counter gets to 0 as follows:
MessageFragment dialog = new MessageFragment();
dialog.show(getSupportFragmentManager(), "MessageFragment");
The main layout of that activity has the following attribute set android:keepScreenOn="true",that way the screen will not timeout as long as the activity is visible. If I then open another application then onStop is called on CounterActivity. If the counter, which is still running in the background, gets to 0 instead of showing the DialogFragment the following exception is thrown:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
You cannot do fragment transactions when your activity is not on screen for UI reasons (your activity may be killed then restored, thus, any UI change won't be made again).
Therefore you must avoid any UI change when your activity is not active. You have multiple solutions to handle the problem :
1-You can simply register a boolean "showDialogOnResume", then override your "public void onResume()" :
#Override
public void onResume() {
super.onResume();
if(showDialogOnResume) {
/*build your dialog here*/
dialog.show(getSupportFragmentManager(), "MessageFragment");
}
}
If you are doing this in activity (not in fragment) i hevily recommand to do override "onPostResume()" instead. Look this post and especially jed answer and pjv comment.
It may even happen that you are not calling :
super.onActivityResult();
On your "onActivityResult()"; as Sufian said in his comment to jed answer.
Basically : avoid any UI change during activity lifecycle. Allow it only when your activity is active. If you cannot do that (other threads like AsyncTasks), get the answer, save it somehow, and perform your change on your activity result. Because if the user never resume your activity, you don't need to do your changes.

Categories

Resources