39

I would like to know how to implement splash screen using Navigation Architecture Component.

Till now I have something like this

enter image description here

A user has to setup his profile in ProfileFragment for the first time and can edit their profile from ChatFragment.

My problem is I don't know how to remove SplashFragment from stack after navigation. I've seen conditional navigation but didn't quite understand.

Yaswant Narayan
  • 1,297
  • 1
  • 15
  • 20

5 Answers5

50

There is a common misuse of the splash screen when it is shown to the user for some amount of seconds, and users are wasting their time looking at the Splash screen while they could already interact with the App. Instead of that, you should get them ASAP to the screen where they could interact with the App. Because of that previously Splash screen was considered anti-pattern on android. But then Google realized that there still exist short window between user had clicked on the icon and your first App screen is ready for interaction, and during that time you can show some branding information. This is the right way to implement a Splash screen.

So to implement Splash screen the right way you don't need separate Splash Fragment as it will introduce an unnecessary delay in App loading. To do it you will need only special theme. In theory App theme could be applied to UI and becomes visible much sooner than your App UI will be initialized and become visible. So in a nutshell, you just need SplashTheme like this:

<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
    <item name="android:windowBackground">@drawable/splash_background</item>
</style>

splash_background drawable should be like this:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:opacity="opaque"> <!-- android:opacity="opaque" should be here -->
    <item>
        <!--this is your background, you can use color, gradient etc.-->
        <color android:color="@color/colorPrimary"/>
        <!--<shape>
              <gradient
                   android:angle="315"
                   android:endColor="#1a82ff"
                   android:startColor="#2100d3"
                   android:type="linear"/>
        </shape> -->
    </item>
    <item>
        <bitmap android:src="@drawable/ic_logo"
                android:gravity="center"/>
    </item>
</layer-list>

Your fragments will be anyway hosted in some activity, let's call it MainActivty. In the Manifest just add SplashTheme to your Activity (that will show the splash screen theme from the moment user had clicked the App icon) :

<activity android:name=".ui.MainActivity"
              android:theme="@style/SplashTheme">

Then in MainActivity to return to your regular AppTheme do this in onCreate before super call :

override fun onCreate(savedInstanceState: Bundle?) {
    setTheme(R.style.AppTheme)
    super.onCreate(savedInstanceState)
    .....
Roman Nazarevych
  • 7,513
  • 4
  • 62
  • 67
  • I thought the idea of Nav AAC is to be able to use single activity architecture. Are you sure you don't just need a graph and popTo Inclusive? – EpicPandaForce Feb 18 '19 at 22:52
  • 2
    @EpicPandaForce First of all, AAC is not about single activity, Single Activity just fits the best on AAC, you can use it with multiple activities as well. And most important is that fact that with SplashFragment you cannot implement proper Splash screen. – Roman Nazarevych Feb 18 '19 at 23:05
  • 1
    `And most important is that fact that with SplashFragment you cannot implement proper Splash screen.` why not? Also, see https://android-developers.googleblog.com/2018/05/use-android-jetpack-to-accelerate-your.html?m=1 and *"""we are introducing the Navigation component as a framework for structuring your in-app UI, with a focus on making a single-Activity app the preferred architecture."""* – EpicPandaForce Feb 18 '19 at 23:36
  • 1
    Read this article https://plus.google.com/+AndroidDevelopers/posts/Z1Wwainpjhd pointed also by @Sander, there is explain what is a proper Splash screen. Briefly with `SplashFragment` you will have unnecessary delays while starting the App, with Splash screen implemented via App theme will be shown only for the time needed for the App to be initialized. – Roman Nazarevych Feb 19 '19 at 07:56
  • 8
    Okay, that is completely dependant on what your business requirements are. It is common pattern to use explicit Splash screen to have a place where you determine whether you are logged in or not. – EpicPandaForce Feb 19 '19 at 11:36
  • 1
    The SplashTheme provides a kind of Splash pattern for when Android/your-app is in struggle-town initialising. Often that'll only really need to happen at first time startup or perhaps when an update needs to migrate DBs, etc (that was how they (Google) originally sold it at least). You might want to use a Shim (or headless Activity) or a custom Splash Activity to determine the next step based on logic, or even do stupid stuff like block app usage for some seconds while displaying some awful branding that only upper management appreciate (seasoned Android dev here). – straya Jun 22 '19 at 12:07
  • Your tutorial link is broken now that Google+ is gone. – Rafa Viotti Sep 05 '19 at 13:51
  • @RomanNazarevych Ok, but what if I need to initialize some data and on the basis of these choose which fragment to navigate to? The problem is that `setTheme(R.style.AppTheme)` is legit only just immediately in `onCreate`, so if the data-initializer is a long-running task i should block the UI because i will not be able to use setTheme asynchronously. – blow May 31 '21 at 16:53
  • this not working with bottom navigation view, getting ` java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mml.foody/com.mml.foody.ui.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.appcompat.app.ActionBar.setTitle(java.lang.CharSequence)' on a null object reference` – MML Aug 05 '21 at 01:24
  • 1
    @EpicPandaForce and what? You can have some `StarterFragment` (without any UI, just to detect which screen is next) or set `startDestination` of `NavGraph` programmatically (dynamically) inside `MainActivity` to open `LoginFragment` or `HomeFragment` (based on login status) BUT for splash you still have to use `Theme` in any case! Don't create any UI using `SplashFragment`! – user924 Aug 30 '21 at 08:24
29

You can try this, it currently works for me:

 <action
        android:id="@+id/action_splashFragment_to_profileFragment"
        app:destination="@id/signInFragment"
        app:launchSingleTop="true"
        app:popUpTo="@id/splashFragment"
        app:popUpToInclusive="true" />

Just set the popUpTo(the current destination) and popUpToInclusive(true), this will pop all other screens until it gets to the specified destination - also pop the destination if popUpToInclusive() is set to true - before navigating to the new destination.

Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
Jerry Okafor
  • 3,710
  • 3
  • 17
  • 26
  • Setting current fragment name in `app:popUpTo` did the trick! – Ankur Jul 28 '21 at 16:47
  • This is the answer that worked for me. Thanks. I would get splash then a blank page then HomeFragment. Other answers say use Apptheme but I need to do some stuff before Home and before display. – JPM Oct 29 '21 at 19:27
16

Splash screens have always been weird on Android. You only want to show a splash screen between clicking the app icon and the creation of the first Activity. If your splash screen is a Fragment the user will still see the white background until the first Activity is created and the startup time of your app will increase because a splash Fragment has to be created and removed. The best practice is to use a splash AppTheme like Ian Lake (an Android Framework Engineer) explains in this post.

As for navigation, your app should have a fixed destination which is the first and last screen a user sees when entering and exiting your app like explained in the principles of navigation. In your case it would make sense to make the ChatFragment the fixed destination. In the onCreate of the ChatFragment you should check if the user already has a profile and redirect them to the ProfileFragment using conditional navigation if they don't.

Sander
  • 808
  • 6
  • 18
  • Doesn't work if you need splash to do something before the activity like credentials.. – JPM Oct 29 '21 at 19:17
8

Here is two way to solve the above situation.

One: In the NavHostFragment's Activity, override the onBackPress() method,adn if the current NavDestination is MainFragment then just finish() the Activity.

@Override
public void onBackPressed() {
    NavDestination navDestination = mNavController.getCurrentDestination();
    if (navDestination != null
            && navDestination.getId() == R.id.mainFragment) {
        finish();
        return;
    }
    super.onBackPressed();
}

Two: Set the to MainFragment action in Navigation_graph app:popUpTo="@id/nav_graph" and app:popUpToInclusive="true"

<?xml version="1.0" encoding="utf-8"?>
<navigation
    android:id="@+id/nav_graph"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@id/splashFragment">

    <fragment
        android:id="@+id/splashFragment"
        android:name="xxx.fragment.splash.SplashFragment"
        android:label="fragment_splash"
        tools:layout="@layout/fragment_splash">
        <action
            android:id="@+id/action_splashFragment_to_mainFragment"
            app:destination="@id/mainFragment"
            app:enterAnim="@anim/anim_right_in"
            app:exitAnim="@anim/anim_left_out"
            app:popEnterAnim="@anim/anim_left_in"
            app:popExitAnim="@anim/anim_right_out"
            app:popUpTo="@id/nav_graph"
            app:popUpToInclusive="true"/>
        <action
            android:id="@+id/action_splashFragment_to_guideFragment"
            app:destination="@id/guideFragment"
            app:enterAnim="@anim/anim_right_in"
            app:exitAnim="@anim/anim_left_out"
            app:popEnterAnim="@anim/anim_left_in"
            app:popExitAnim="@anim/anim_right_out"
            app:popUpTo="@id/nav_graph"
            app:popUpToInclusive="true"/>
    </fragment>

    <fragment
        android:id="@+id/guideFragment"
        android:name="xxx.fragment.guide.GuideFragment"
        android:label="GuideFragment"
        tools:layout="@layout/fragment_guide">
        <action
            android:id="@+id/action_guideFragment_to_mainFragment"
            app:destination="@id/mainFragment"
            app:enterAnim="@anim/anim_right_in"
            app:exitAnim="@anim/anim_left_out"
            app:popEnterAnim="@anim/anim_left_in"
            app:popExitAnim="@anim/anim_right_out"
            app:popUpTo="@id/nav_graph"
            app:popUpToInclusive="true"/>
    </fragment>

    <fragment
        android:id="@+id/mainFragment"
        android:name="xxx.fragment.main.MainFragment"
        android:label="fragment_main"
        tools:layout="@layout/fragment_main">

    </fragment>

</navigation>

Wish for help!

Warning:this is deprecated. In Navigation alpha08, the clearTask tag is removed:Remove the deprecated clearTask and launchDocument flags from NavOptions

In my project, I test app:clearTask="true" in the action,and it works well.~~~

<fragment
    android:id="@+id/splashFragment"
    android:name="xxx.SplashFragment"
    android:label="fragment_splash"
    tools:layout="@layout/fragment_splash">
    <action
        android:id="@+id/action_splashFragment_to_mainFragment"
        app:destination="@id/mainFragment"
        app:enterAnim="@anim/anim_right_in"
        app:exitAnim="@anim/anim_left_out"
        app:popEnterAnim="@anim/anim_left_in"
        app:popExitAnim="@anim/anim_right_out"
        app:clearTask="true"/>
</fragment>
peerless2012
  • 179
  • 1
  • 7
2

You can try this trick it's woking absolutely fine for me. Make your splash screen as launcher screen from the manifest file and start your host activity form the splash screen.

Manifest file code

<activity android:name=".SplashScreen"
    android:theme="@style/Theme.AppCompat.Light.NoActionBar">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

In your java file

new Handler().postDelayed(new Runnable(){
    @Override
    public void run() {
        /* Create an Intent that will start the Navigation host activity . */
        Intent mainIntent = new Intent(getApplicationContext(), MainActivity.class);
        startActivity(mainIntent);
        finish();
    }
}, 5000);

Xml code of your host activity

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/navigation_graph" />

</android.support.constraint.ConstraintLayout>

And you navigation will be like this

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/navigation_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.devgenesis.breaker.ice.navigationmproject.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" />

</navigation>
  • Add a click listener or means for Users to move beyond the Splash faster and you redeem some dignity with this approach, otherwise it's a UX anti-pattern: you block app usage for (probably) the Marketing Team to hit some misaligned KPI. – straya Jun 22 '19 at 12:15