Android 12 Splash Screen API inconsistent behavior - android

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.

Related

How to avoid recreating activity when the screen has rotated?

I use single Activity pattern in my app using Navigation component. I use YouTube Android library for playing the video. When I click full screen icon on video player the top and bottom tool bars have to be gone and the screen has to be changed on landscape mode. But after the screen has rotated the activity was recreated and video stops and starts over. The question is how to keep playing the video after the screen has rotated?
I found one solution to add configChanges to the manifest file
<activity
android:name=".ui.MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This solved my problem, the activity stopped being recreated when the screen was rotated. But I do not want this behavior in all fragments, I need it only in the fragment where the video player is located.
This is my code in Fragment:
private fun fullScreenListener() {
val decorView = activity?.window?.decorView?.let {
val screenListener = object : YouTubePlayerFullScreenListener {
override fun onYouTubePlayerEnterFullScreen() {
binding.youtubePlayer.enterFullScreen()
hideSystemUi(it)
}
override fun onYouTubePlayerExitFullScreen() {
showSystemUi(it)
}
}
binding.youtubePlayer.addFullScreenListener(screenListener)
}
}
private fun hideSystemUi(view: View) {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
MainActivity.hideBottomNavBar()
WindowCompat.setDecorFitsSystemWindows(requireActivity().window, false)
WindowInsetsControllerCompat(requireActivity().window,view).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
private fun showSystemUi(view: View) {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
MainActivity.showBottomNavBar()
WindowCompat.setDecorFitsSystemWindows(requireActivity().window, true)
WindowInsetsControllerCompat(requireActivity().window, view).show(WindowInsetsCompat.Type.systemBars())
}
You really do not want to do this. The problem isn't just the restart on rotation, it's that there's at LEAST a dozen situations that can cause an Activity restart, and you can't block some of them. On Android this is really just something you need to live with, and learn how to code to make it cleanly restart.
And no, you can't do configChanges at runtime or only for some fragments. It works on an Activity level.
Instead, you should ask a different question- tell use what isn't working when you rotate, and ask how to fix that with restart.
Based on your new answer- I'm surprised your video view doesn't support this without work. However, if you implement onSaveInstanceState to save the seek time of the video and onRestoreInstanceState to seek to that time, it should work with at most a brief hiccup as it reads in the video.
Sorry i am not a kotlin developer, but this solution fixed mine. But before i post the codes, let me explain it to you, although it's not the most reliable but a better option.
Note: If you add these lines to your manifest, there are a lot of android configuration changes that will not be handled by below lines.
Take for instance you added this line to your manifest file.
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout">
Now, i'll prove to you that adding above line is not an option at all:
Firstly, assuming this line is still in your manifest file, then if your app targets Api level 29 and above, toggle the android system ui dark mode which is located in Settings > Display & Brightness > Dark theme then return back to your app and you'll notice that your activity has been recreated and the video restarts.
Now, to avoid that, then you'll need to add Uimode to the above line of code.
android:configChanges="uimode|orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
(Notice the difference between it and the first code)
Now that you've added uimode to configChanges, the particular activity won't be able to detect changes when the android system ui dark mode switch is toggled. But it's still not the best because it will cause the following:
A bad user experience whereby if a user switches theme but theme changes doesn't reflect in your app.
Let's assume that you have an Alert dialog that's still showing and you rotate your screen, the width tends to overlap the screen due to the smallestScreenSize | screenLayout attribute.
Let's assume that you're onMultiWindowChanged, it'll cause bad user experience too wherby the activity will want to resize and recreate the screen ui layout in order to adjust to the multi window mode but you'll end up seeing overlaps.
Anyways, there are so many configuration changes that will cause activity to restart and instead of adding this line and changing the configChanges attribute everytime just make use of the:
onSavedInsatnceState and onRestoreInstanceState attributes or
Make use of android new method of saving ui state which is viewModel and savedStateHandle
Now, if you want use method 1, you need to understand Android lifecycle architecture component first then use the onSavedInsatnceState to save and use the onRestoreInsatnceState to restore the ui states. But according to https://developer.android.com/reference/android/app/Activity
Starting with Honeycomb, an application is not in the killable state until its onStop() has returned. This impacts when onSaveInstanceState(android.os.Bundle) may be called (it may be safely called after onPause()) and allows an application to safely wait until onStop() to save persistent state.
Declare this as global variable
private final String KEY_YOUTUBE_VIDEO_LENGTH_STATE = "youtube_length_state";
Override onSavedInsatnceState method and add below codes.
#Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
// save YouTube video length state
long videoStateLong = binding.youtubePlayer.getVideoLength();
savedInstanceState.putLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE, videoStateLong);
//Call below line to save bundle
super.onSaveInstanceState(savedInstanceState);
}
Then override onRestoreInstanceState and add below lines.
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Retrieve video state and it's length.
if(savedInstanceState != null) {
binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}
Finally, incase onRestoreInstanceState is not called then override onResume method and add below lines of codes.
#Override
protected void onResume() {
super.onResume();
Bundle savedInstanceState = new Bundle();
if (savedInstanceState != null) {
binding.youtubePlayer.getVideoLength.onRestoreInstanceState(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}
}
Now, in the onCreate method, add below lines
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState==null){
playVideoFromBeginning(); // No video length is saved yet, play video from beginning
}else{
restoreVideoPreviousLength(savedInstanceState); // Restore video length found in the Bundle and pass savedInstanceState as an argument
}
}
public void restoreVideoPreviousLength(Bundle savedInstanceState) {
binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}
Note:
codes in onCreate method will only work for screen rotations but those in onResume will work for uimode change etc.
onSavedInsatnceState and onRestoreInstanceState should NEVER be used to store large datasets like fetching Recyclerview items. ViewModel should be used in cases like this instead.
Now, if you want to use the second method which is viewModel method:
Firstly, understand Android viewModel Android MVVM design overview which includes:
The Android livedata https://developer.android.com/topic/libraries/architecture/livedata
The Saved State Module for ViewModel
The ViewModel
Note: ViewModel's only responsibility is to manage the data for the UI. It should never access your view hierarchy or hold a reference back to the Activity or the Fragment.
Now you can learn more from here https://www.geeksforgeeks.org/viewmodel-with-savedstate-in-android/
Remember, i'm not a kotlin developer

Kotlin: Load/initialize next activity in the background

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.

Xamarin Splash screen example does not work in landscape mode on a phone. How to fix it?

I downloaded the splash screen example from the Xamarin website:
http://developer.xamarin.com/guides/android/user_interface/creating_a_splash_screen/
I compiled it and ran it on my phone:
http://www.gsmarena.com/samsung_galaxy_fresh_s7390-5841.php
It was working fine when holding my phone in portrait mode (vertical). The splash screen became directly visible and after a few seconds, the view with the button became visible. When closing and restarting the application, it was still working fine.
After that, I closed it again and hold my phone in landscape (horizontal) mode. Now, I started the application again. My phone was frozen for a few seconds, the splash did not become visible. After that, I saw my view with the button.
When you try to reproduce this issue, make sure that you:
Do not try to reproduce it on a virtual device (the behavior is different).
Make sure that the sleep takes at least 10 seconds, then you really see what the problem is: a frozen application instead of a splash screen.
If you do not have the Samsung Trend Lite, you try it on another small smart phone. I find it hard to imagine that this could be a "Samsung Trend Lite only" issue.
How do I fix this?
The Xamarin sample that you linked has big problem within it:
[Activity(Theme = "#style/Theme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Thread.Sleep(10000); // Simulate a long loading process on app startup.
StartActivity(typeof(Activity1));
}
}
The OnCreate() method of an Activity is executed on the UI thread therefore calling a Thread.Sleep() inside it will lock up the main thread, possibly generating an Application Not Responding (ANR) to be display to the user.
This is fault in the Xamarin docs, you should not run a Thread.Sleep() on the UI thread, especially within one of the core lifecycle callbacks for an activity.
Fix this by using a background thread to execute the sleep and then call back into the splash activity to launch the next activity:
[Activity(Theme = "#style/Theme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
System.Threading.Tasks.Task.Run( () => {
Thread.Sleep(10000); // Simulate a long loading process on app startup.
StartActivity(typeof(Activity1));
});
}
}
I just discovered another solution. It is a bit strange to use a timer but it solves the problem.
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
System.Timers.Timer t = new System.Timers.Timer(1);
t.Elapsed += (o, e) =>
{
t.Stop();
Thread.Sleep(10000); // Simulate a long loading process on app startup.
StartActivity(typeof(Activity1));
};
t.Start();
}

Show a splash screen with MvvmCross after Android purged an application from memory

Here is my scenario: when my app gets purged from memory by the OS and its reopened from the recent apps list, it goes straight to the Activity that was last open. When this happens I would like to initialize a couple of business objects.
My first approach to achieve this was to do the initialization in the InitializeLastChance method in the Setup class.
Inside the InitializeLastChance method I check if the current top activity is the MvxSplashScreenActivity. If it's not, I make the necessary initialization. The code is something like this:
protected override void InitializeLastChance()
{
try
{
var topActivity = Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity;
if (topActivity.LocalClassName != _splashScreenName)
{
//Do special initialization
}
}
catch (Exception ex)
{
//Log error
}
base.InitializeLastChance();
}
This works as expected but the visual effect isn't the best: a blank screen with the name of the app appears for a couple of seconds while the initialization occurs. When the initialization is finished, the last open activity is loaded.
To avoid this blank screen, I wanted to try a different approach: show an Activity while the initialization is being done, similar to a splash screen, so that the user can have some feedback that something is happening. In this approach the ViewModel attached to the new Activity would do the initialization that I want.
To show this new activity, I tried creating a custom IMvxAppStart and register it in the App class, but that hasn't worked. My custom IMvxAppStart has the following code:
public class CustomAppStart
: MvxNavigatingObject
, IMvxAppStart
{
public void Start(object hint = null)
{
if (hint == null)
{
ShowViewModel<LoginViewModel>();
}
else
{
ShowViewModel<InitializationViewModel>();
}
}
}
I wanted to show the LoginViewModel when the app starts from scratch and in the cases where the app goes straight to the last loaded Activity, the InitializationViewModel would be shown. In the latter scenario the Start method isn't called. I've checked the MvvmCross source code and as far as I can understand, the Start method is called by the MvxSplashScreenActivity (correct me if I'm wrong). When an app is reopened and goes straight to the last opened Activity, the MvxSplashScreenActivity isn't used so the Start method in CustomAppStart is not called.
That being said, I have some questions regarding the CustomAppStart approach:
Is this approach the best way to show a splash screen or "initialization activity" in the cases where the app goes straight to the last opened activity?
How do I use the CustomAppStart in these situations, since it seems that the Start method is called only from the MvxSplashScreenActivity?
How do I pass a value to the hint parameter in the Start method?
Thanks

Making a click-through Activity

I'm trying to adjust the brightness of my phone programmatically. I've got an Activity in my app which is translucent to do this, however users can't click through it to the activity/window beneath it. I kill this activity as soon as the brightness is set, however I want to do more work in this activity (such as gradual brightness adjustment) which requires some time, and I don't want the user tapping at their phone wondering why their actions aren't being registered.
So basically, I need to either create a mock Window which will successfully allow me to adjust screen brightness without being displayed, or work out how to make an Activity click-through. I'm not sure how to do either.
BTW, this is the code making the brightness adjustments in the Activity:
android.provider.Settings.System.putInt(getContentResolver(),
android.provider.Settings.System.SCREEN_BRIGHTNESS, Math.round(SOME_BRIGHTNESS * 255f));
Window window = getWindow();
window.getAttributes().screenBrightness = SOME_BRIGHTNESS;
window.setAttributes(window.getAttributes());
float sysBrightPer = getSystemBrightness(getApplicationContext());
new Thread() {
public void run() {
try {
sleep(BRIGHT_TIMEOUT);
} catch (InterruptedException e) {
e.printStackTrace();
}
finish();
}
}.start();
Any advice?
P.S. I found this app on the market. I wonder if the way this has been achieved would help me? https://market.android.com/details?id=com.haxor
Hah! That app did help me, if only because it led me to this solution!
Brightness Screen Filter
For the click lazy, use this:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
I'm afraid this no longer works in 4.0+, if applied to the window itself (presumably to the prevent use of activity class methods like onkey from a transparent overlay). However one can still use windowmanager.addview or layoutinflater.inflate to add a custom view class which extends viewgroup, and if you use layoutparams.FLAG_NOT_TOUCHABLE in adding or inflating this view, it will be click-through, and remain on top even when the activity that called it goes into onpause.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
If you want to intercept touches on specific views you can use the window manager to add a view with different flags:
e.g.
WindowManager.LayoutParams params = new WindowManager.LayoutParams(...);
getWindowManager().addView(myButton,params);

Categories

Resources