$30 off During Our Annual Pro Sale. View Details »

Launch Screens: From a Tap to Your App

Launch Screens: From a Tap to Your App

The launch screen is a user’s first experience of your application and, hence, should be designed with great care. In this talk, we will deep dive into the concept of launch screen, discover how to measure, debug & optimise them efficiently, and learn more about how to implement them correctly. In other words, this talk is all about discussing for ±45 min about screens displayed less than 5 seconds.

Cyril Mottier

June 17, 2016
Tweet

More Decks by Cyril Mottier

Other Decks in Programming

Transcript

  1. @cyrilmottier
    Launch screens
    From a Tap to your App

    View Slide

  2. 38
    sec
    average
    interaction time

    View Slide

  3. 23
    %
    apps used
    only once
    38
    sec
    average
    interaction time

    View Slide

  4. 13
    launches per
    month
    23
    %
    apps used
    only once
    38
    sec
    average
    interaction time
    Source: Mobile Life Research Centre, University of Stockholm & Localytics

    View Slide

  5. Polish your
    launching experience

    View Slide

  6. View Slide

  7. Placeholder UI
    /ˈpleɪshəʊldə(r) juː-aɪ/
    Display core structural elements (status bar, app bar, etc.) without
    content until the app has loaded.

    View Slide

  8. Branded launch
    /ˈbrændɪd lɔːntʃ/
    Display your logo or other elements that improve brand recognition to
    portray a brand while the app is loading.

    View Slide

  9. Branded launch
    Google Maps
    Placeholder UI
    Settings

    View Slide

  10. Branded launch
    Google Maps
    Placeholder UI
    Settings
    your side
    Choose

    View Slide

  11. Branded launch
    Google Maps
    Placeholder UI
    Settings

    View Slide

  12. Yes!
    A launch screen is intentionally boring

    View Slide

  13. Source: iOS Human Interface Guidelines, Apple
    If you think that following these guidelines will
    result in a plain, boring launch image, you’re right.
    Remember, the launch image doesn’t provide you
    with an opportunity for artistic expression. It’s
    solely intended to enhance the user’s perception of
    your app as quick to launch and immediately
    ready for use.

    View Slide

  14. Timeline of a launch

    View Slide

  15. Timeline of a launch
    Slowed down version

    View Slide

  16. User taps
    on icon
    Timeline of a launch
    1 2 3 4 5

    View Slide

  17. User taps
    on icon
    Timeline of a launch
    1 2
    Fit to screen
    animation starts
    3 4 5

    View Slide

  18. User taps
    on icon
    Timeline of a launch
    1 2
    Fit to screen
    animation starts
    3 4 5
    Fit to screen
    animation ends

    View Slide

  19. User taps
    on icon
    Timeline of a launch
    1 2
    Fit to screen
    animation starts
    3 4 5
    Fit to screen
    animation ends
    Window
    switching starts

    View Slide

  20. User taps
    on icon
    Timeline of a launch
    1 2
    Fit to screen
    animation starts
    3 4 5
    Fit to screen
    animation ends
    Window
    switching starts
    App content
    finishes loading

    View Slide

  21. User taps
    on icon
    Timeline of a launch
    1 2
    Fit to screen
    animation starts
    3 4 5
    Fit to screen
    animation ends
    Window
    switching starts
    App content
    finishes loading

    View Slide

  22. 1 2 3 4 5
    Launcher & Intent triggering
    Entering animation
    Static launch screen
    Content switching / fading
    Application fully drawn
    1
    2
    3
    4
    5

    View Slide

  23. 1 2 3 5
    System App
    Launcher & Intent triggering
    Entering animation
    Static launch screen
    Content switching / fading
    Application fully drawn
    1
    2
    3
    4
    5
    4

    View Slide

  24. 5
    The perfect launch
    1 2
    3
    4

    View Slide

  25. 1 Load and launch the app
    2 Display a starting/preview window
    3 Create the app process
    4 Create the app object
    5 Launch the main thread
    6 Create the main activity
    7 Inflate views
    8 Lay out the screen
    9 Perform the initial draw

    View Slide

  26. 1 Load and launch the app
    2 Display a starting/preview window
    3 Create the app process
    4 Create the app object
    5 Launch the main thread
    6 Create the main activity
    7 Inflate views
    8 Lay out the screen
    9 Perform the initial draw

    View Slide

  27. System
    1 Load and launch the app
    2 Display a starting/preview window
    3 Create the app process
    4 Create the app object
    5 Launch the main thread
    6 Create the main activity
    7 Inflate views
    8 Lay out the screen
    9 Perform the initial draw

    View Slide

  28. System
    App
    1 Load and launch the app
    2 Display a starting/preview window
    3 Create the app process
    4 Create the app object
    5 Launch the main thread
    6 Create the main activity
    7 Inflate views
    8 Lay out the screen
    9 Perform the initial draw

    View Slide

  29. System
    App
    Cold start
    1 Load and launch the app
    2 Display a starting/preview window
    3 Create the app process
    4 Create the app object
    5 Launch the main thread
    6 Create the main activity
    7 Inflate views
    8 Lay out the screen
    9 Perform the initial draw

    View Slide

  30. System
    App
    Warm start
    1 Load and launch the app
    2 Display a starting/preview window
    3 Create the app process
    4 Create the app object
    5 Launch the main thread
    6 Create the main activity
    7 Inflate views
    8 Lay out the screen
    9 Perform the initial draw

    View Slide

  31. PhoneWindowManager.java
    Context context = mContext;
    try {
    context = context.createPackageContext(packageName, 0);
    context.setTheme(theme);
    } catch (PackageManager.NameNotFoundException e) {
    // Ignore
    }
    final PhoneWindow win = new PhoneWindow(context);
    win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
    win.setFlags(/*...*/);
    final WindowManager.LayoutParams params = win.getAttributes();
    // Tweak params
    ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).
    addView(win.getDecorView(), params);

    View Slide

  32. PhoneWindowManager.java
    Context context = mContext;
    try {
    context = context.createPackageContext(packageName, 0);
    context.setTheme(theme);
    } catch (PackageManager.NameNotFoundException e) {
    // Ignore
    }
    final PhoneWindow win = new PhoneWindow(context);
    win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
    win.setFlags(/*...*/);
    final WindowManager.LayoutParams params = win.getAttributes();
    // Tweak params
    ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).
    addView(win.getDecorView(), params);
    Create Context for the
    started application
    context = context.createPackageContext(packageName, 0);

    View Slide

  33. PhoneWindowManager.java
    Context context = mContext;
    try {
    context = context.createPackageContext(packageName, 0);
    context.setTheme(theme);
    } catch (PackageManager.NameNotFoundException e) {
    // Ignore
    }
    final PhoneWindow win = new PhoneWindow(context);
    win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
    win.setFlags(/*...*/);
    final WindowManager.LayoutParams params = win.getAttributes();
    // Tweak params
    ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).
    addView(win.getDecorView(), params);
    Set the target theme
    context.setTheme(theme);

    View Slide

  34. PhoneWindowManager.java
    Context context = mContext;
    try {
    context = context.createPackageContext(packageName, 0);
    context.setTheme(theme);
    } catch (PackageManager.NameNotFoundException e) {
    // Ignore
    }
    final PhoneWindow win = new PhoneWindow(context);
    win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
    win.setFlags(/*...*/);
    final WindowManager.LayoutParams params = win.getAttributes();
    // Tweak params
    ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).
    addView(win.getDecorView(), params);
    final PhoneWindow win = new PhoneWindow(context);
    win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
    win.setFlags(/*...*/);
    Create new Window
    and set it up
    final WindowManager.LayoutParams params = win.getAttributes();
    // Tweak params

    View Slide

  35. PhoneWindowManager.java
    Context context = mContext;
    try {
    context = context.createPackageContext(packageName, 0);
    context.setTheme(theme);
    } catch (PackageManager.NameNotFoundException e) {
    // Ignore
    }
    final PhoneWindow win = new PhoneWindow(context);
    win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
    win.setFlags(/*...*/);
    final WindowManager.LayoutParams params = win.getAttributes();
    // Tweak params
    ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).
    addView(win.getDecorView(), params);
    ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).
    addView(win.getDecorView(), params);
    Display the newly
    created Window

    View Slide

  36. Same look,
    different process
    …and that explains a lot

    View Slide

  37. $ adb logcat | grep “ActivityManager”
    ActivityManager: Displayed com.launchtime/.LaunchTimeActivity: +666ms
    API 19+

    View Slide

  38. View Slide

  39. public class LaunchTimeActivity extends AppCompatActivity
    implements LoaderManager.LoaderCallbacks {


    private static final int LOADER_YO = 0xcafe;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_launch_time);

    getSupportLoaderManager().initLoader(LOADER_YO, null, this);

    }


    @Override

    public void onLoadFinished(Loader loader, Void data) {

    switch (loader.getId()) {

    case LOADER_YO:

    // ...

    break;

    }

    reportFullyDrawn();

    }

    // ...
    }

    View Slide

  40. public class LaunchTimeActivity extends AppCompatActivity
    implements LoaderManager.LoaderCallbacks {


    private static final int LOADER_YO = 0xcafe;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_launch_time);

    getSupportLoaderManager().initLoader(LOADER_YO, null, this);

    }


    @Override

    public void onLoadFinished(Loader loader, Void data) {

    switch (loader.getId()) {

    case LOADER_YO:

    // ...

    break;

    }

    reportFullyDrawn();

    }

    // ...
    }
    reportFullyDrawn();

    View Slide

  41. public class LaunchTimeActivity extends AppCompatActivity
    implements LoaderManager.LoaderCallbacks {


    private static final int LOADER_YO = 0xcafe;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_launch_time);

    getSupportLoaderManager().initLoader(LOADER_YO, null, this);

    }


    @Override

    public void onLoadFinished(Loader loader, Void data) {

    switch (loader.getId()) {

    case LOADER_YO:

    // ...

    break;

    }

    reportFullyDrawn();

    }

    // ...
    }
    SecurityException
    on KitKat…
    reportFullyDrawn();

    View Slide

  42. public final class ActivityCompatAdditions {


    private static final Impl IMPL;


    static {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

    IMPL = new LollipopImpl();

    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

    IMPL = new KitKatImpl();

    } else {

    IMPL = new BaseImpl();

    }

    }


    private ActivityCompatAdditions() {

    // No instances

    } 


    public static void reportFullyDrawn(Activity activity) {

    IMPL.reportFullyDrawn(activity);

    } 


    private interface Impl {

    void reportFullyDrawn(Activity activity);

    } 


    View Slide

  43. private static final class BaseImpl implements Impl {

    @Override

    public void reportFullyDrawn(Activity activity) {

    // No-op

    }

    }


    private static final class KitKatImpl implements Impl {

    @Override @TargetApi(Build.VERSION_CODES.KITKAT)

    public void reportFullyDrawn(Activity activity) {

    try {

    activity.reportFullyDrawn();

    } catch (SecurityException se) {

    // Prevent an Exception on KitKat

    }

    }

    }


    private static final class LollipopImpl implements Impl {

    @Override @TargetApi(Build.VERSION_CODES.KITKAT)

    public void reportFullyDrawn(Activity activity) {

    activity.reportFullyDrawn();

    }

    }


    }

    View Slide

  44. public class LaunchTimeActivity extends AppCompatActivity
    implements LoaderManager.LoaderCallbacks {


    private static final int LOADER_YO = 0xcafe;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_launch_time);

    getSupportLoaderManager().initLoader(LOADER_YO, null, this);

    }


    @Override

    public void onLoadFinished(Loader loader, Void data) {

    switch (loader.getId()) {

    case LOADER_YO:

    // ...

    break;

    }


    }

    // ...
    }
    reportFullyDrawn();

    View Slide

  45. public class LaunchTimeActivity extends AppCompatActivity
    implements LoaderManager.LoaderCallbacks {


    private static final int LOADER_YO = 0xcafe;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_launch_time);

    getSupportLoaderManager().initLoader(LOADER_YO, null, this);

    }


    @Override

    public void onLoadFinished(Loader loader, Void data) {

    switch (loader.getId()) {

    case LOADER_YO:

    // ...

    break;

    }


    }

    // ...
    }
    reportFullyDrawn();

    View Slide

  46. public class LaunchTimeActivity extends AppCompatActivity
    implements LoaderManager.LoaderCallbacks {


    private static final int LOADER_YO = 0xcafe;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_launch_time);

    getSupportLoaderManager().initLoader(LOADER_YO, null, this);

    }


    @Override

    public void onLoadFinished(Loader loader, Void data) {

    switch (loader.getId()) {

    case LOADER_YO:

    // ...

    break;

    }

    }

    // ...
    }
    ActivityCompatAdditions.reportFullyDrawn(this);

    View Slide

  47. $ adb logcat | grep “ActivityManager”
    ActivityManager: Displayed com.launchtime/.LaunchTimeActivity: +666ms
    ActivityManager: Fully drawn com.launchtime/.LaunchTimeActivity: +1s440ms
    API 19+

    View Slide

  48. com.launchtime/.LaunchScreenActivity
    $ adb shell am start

    View Slide

  49. com.launchtime/.LaunchScreenActivity
    $ adb shell am start -W

    View Slide

  50. View Slide

  51. View Slide

  52. $ adb shell am start -W com.launchtime/.LaunchScreenActivity
    Starting: Intent { act=android.intent.action.MAIN
    cat=[android.intent.category.LAUNCHER]
    cmp=com.launchtime/.LaunchScreenActivity }
    Status: ok
    Activity: com.launchtime/.LaunchScreenActivity
    ThisTime: 2426
    TotalTime: 2426
    WaitTime: 2488
    Complete

    View Slide

  53. $ adb shell screenrecord /sdcard/launch.mp4
    API 19+

    View Slide

  54. $ adb shell screenrecord /sdcard/launch.mp4
    --bugreport
    API 21+

    View Slide

  55. API 21+

    View Slide

  56. API 21+

    View Slide

  57. View Slide

  58. 11:16:46.666 f=26 (0)

    View Slide

  59. 11:16:46.666 f=26 (0)
    11:16:46.666
    Frame timestamp

    View Slide

  60. 11:16:46.666 f=26 (0)
    f=26
    Frame number

    View Slide

  61. 11:16:46.666 f=26 (0)
    (0)
    Dropped frames
    count

    View Slide

  62. 11:16:46.666 f=26 (0)
    Frame timestamp
    Dropped frames
    count
    Frame number

    View Slide

  63. SystemClock.sleep(2000L)
    all the things \o/
    in debug only…

    View Slide

  64. SystemClock.sleep(2000L)
    all the things \o/
    in debug only…

    View Slide

  65. com.launchtime/.LaunchScreenActivity
    $ adb shell am start

    View Slide

  66. com.launchtime/.LaunchScreenActivity
    $ adb shell am start -S

    View Slide

  67. View Slide

  68. View Slide

  69. Developer options
    Background process limit

    View Slide

  70. Developer options
    Background process limit

    View Slide

  71. Force cold launch
    with “No background processes”

    View Slide

  72. Measure, measure
    and measure again!

    View Slide

  73. Use
    Systrace & Traceview
    extensively

    View Slide

  74. Lazy-load & async
    all the things

    View Slide

  75. In general, beware of
    static init & onCreate

    View Slide

  76. public class LazyProvider extends ContentProvider {


    private static final int MODEL = 101;

    private static final int MODEL_ID = 102;


    private static final UriMatcher URI_MATCHER;

    static {

    URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);

    URI_MATCHER.addURI(LazyContract.AUTHORITY, "/model", MODEL);

    URI_MATCHER.addURI(LazyContract.AUTHORITY, "/model/#", MODEL_ID);

    // ...

    }


    private SQLiteOpenHelper mOpenHelper;


    @Override

    public boolean onCreate() {

    mOpenHelper = new LoadOpenHelper(getContext());

    return true;

    }

    // ...

    }
    LazyProvider.java

    View Slide

  77. public class LazyProvider extends ContentProvider {


    private static final int MODEL = 101;

    private static final int MODEL_ID = 102;


    private static final UriMatcher URI_MATCHER;

    static {

    URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);

    URI_MATCHER.addURI(LazyContract.AUTHORITY, "/model", MODEL);

    URI_MATCHER.addURI(LazyContract.AUTHORITY, "/model/#", MODEL_ID);

    // ...

    }


    private SQLiteOpenHelper mOpenHelper;


    @Override

    public boolean onCreate() {

    mOpenHelper = new LoadOpenHelper(getContext());

    return true;

    }

    // ...

    }
    LazyProvider.java
    private static final UriMatcher URI_MATCHER;
    Created at class loading
    but used only in CRUD…

    View Slide

  78. public abstract class LazyGetter {


    private final AtomicReference> mCachedValue =
    new AtomicReference<>(Optional.empty());


    public final T get() {

    Optional value = mCachedValue.get();

    if (!value.isPresent()) {

    synchronized (mCachedValue) {

    value = mCachedValue.get();

    if (!value.isPresent()) {

    mCachedValue.set(Optional.ofNullable(initialValue()));

    }

    }

    }

    return mCachedValue.get().get();

    }


    protected abstract T initialValue();


    }
    LazyGetter.java

    View Slide

  79. public class LazyProvider extends ContentProvider {


    private static final int MODEL = 101;

    private static final int MODEL_ID = 102;


    private static final LazyGetter URI_MATCHER =
    new LazyGetter() {

    @Override

    protected UriMatcher initialValue() {

    final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    uriMatcher.addURI(LazyContract.AUTHORITY, "/model", MODEL);

    uriMatcher.addURI(LazyContract.AUTHORITY, "/model/#", MODEL_ID);

    // ...

    return uriMatcher;

    }

    };

    // ...

    }
    LazyProvider.java

    View Slide

  80. android:windowDisablePreview
    the (wrong) simple way…

    View Slide


  81. xmlns:android="http://schemas.android.com/apk/res/android">


    android:icon="@mipmap/ic_launcher"

    android:label="@string/app_name"

    android:theme="@style/Theme.LaunchTime">


    android:name=".LaunchScreenActivity">










    AndroidManifest.xml

    View Slide


  82. xmlns:android="http://schemas.android.com/apk/res/android">


    android:icon="@mipmap/ic_launcher"

    android:label="@string/app_name"

    android:theme="@style/Theme.LaunchTime">


    android:name=".LaunchScreenActivity">










    Default theme applies
    to all Activities
    AndroidManifest.xml
    android:theme="@style/Theme.LaunchTime">

    View Slide




  83. 
<br/><item name="android:colorPrimary">@color/colorPrimary</item>
<br/><item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
<br/><item name="android:colorAccent">@color/colorAccent</item>
<br/><item name="android:windowBackground">@color/appBackground</item>
<br/>



    values/themes.xml

    View Slide

  84. No project on Earth is
    minSdk 21+ except
    Android built-in apps!

    View Slide

  85. to the rescue…
    AppCompat

    View Slide

  86. to the rescue…
    AppCompat
    (almost)

    View Slide

  87. to the rescue…
    AppCompat
    (almost)

    View Slide

  88. UI design is all about

    View Slide

  89. mirrors
    UI design is all about
    &mirrors
    mirrors
    &
    smoke

    View Slide

  90. Switch between
    compile time & runtime
    themes

    View Slide







  91. values/attrs.xml

    View Slide




  92. name=“PreviewTheme.LaunchTime"
    parent=“android:Theme.Holo.Light.DarkActionBar">


    @dimen/abc_action_bar_default_height_material

    @style/PreviewWidget.ActionBar
    @color/appBackground
    @style/Theme.LaunchTime




    values/themes_preview.xml

    View Slide




  93. name=“PreviewTheme.LaunchTime"
    parent=“android:Theme.Holo.Light.DarkActionBar">


    @dimen/abc_action_bar_default_height_material

    @style/PreviewWidget.ActionBar
    @color/appBackground
    @style/Theme.LaunchTime




    values/themes_preview.xml

    @dimen/abc_action_bar_default_height_material

    Force pre-Lollipop
    ActionBars height
    to 48 / 56 / 64dp

    View Slide




  94. name=“PreviewTheme.LaunchTime"
    parent=“android:Theme.Holo.Light.DarkActionBar">


    @dimen/abc_action_bar_default_height_material

    @style/PreviewWidget.ActionBar
    @color/appBackground
    @style/Theme.LaunchTime




    values/themes_preview.xml
    @style/Theme.LaunchTime
    Define the theme
    to use at runtime

    View Slide




  95. name="PreviewWidget.ActionBar"
    parent=“android:Widget.Holo.Light.ActionBar.Solid.Inverse">

    @color/colorPrimary

    showTitle




    values/styles_preview.xml

    View Slide




  96. name="PreviewTheme.LaunchTime"
    parent=“android:Theme.Material.Light.DarkActionBar">

    @color/colorPrimary

    @color/colorPrimaryDark

    @color/colorAccent

    @color/appBackground

    @style/Theme.LaunchTime




    values-v21/themes_preview.xml

    View Slide




  97. name="PreviewTheme.LaunchTime"
    parent=“android:Theme.Material.Light.DarkActionBar">

    @color/colorPrimary

    @color/colorPrimaryDark

    @color/colorAccent

    @color/appBackground

    @style/Theme.LaunchTime




    values-v21/themes_preview.xml
    parent=“android:Theme.Material.Light.DarkActionBar"
    Use Theme.Material
    on v-21+ devices

    View Slide




  98. name="PreviewTheme.LaunchTime"
    parent=“android:Theme.Material.Light.DarkActionBar">

    @color/colorPrimary

    @color/colorPrimaryDark

    @color/colorAccent

    @color/appBackground

    @style/Theme.LaunchTime




    values-v21/themes_preview.xml
    Define the theme
    to use at runtime
    @style/Theme.LaunchTime

    View Slide


  99. xmlns:android="http://schemas.android.com/apk/res/android">


    android:icon="@mipmap/ic_launcher"

    android:label="@string/app_name"

    android:theme="@style/PreviewTheme.LaunchTime">












    AndroidManifest.xml

    View Slide


  100. xmlns:android="http://schemas.android.com/apk/res/android">


    android:icon="@mipmap/ic_launcher"

    android:label="@string/app_name"

    android:theme="@style/PreviewTheme.LaunchTime">












    AndroidManifest.xml
    android:theme="@style/PreviewTheme.LaunchTime"
    Tell the system to use a
    temporary preview theme

    View Slide

  101. public final class ThemeUtils {
    private ThemeUtils() {
    // No instances
    }
    @UiThread
    public static void ensureRuntimeTheme(Context context) {
    final TypedValue tv = new TypedValue();
    context.getTheme().resolveAttribute(R.attr.ltRuntimeTheme, tv, true);
    if (tv.resourceId <= 0) {
    throw new IllegalArgumentException("ltRuntimeTheme " +
    "not defined in the preview theme");
    }
    context.setTheme(tv.resourceId);
    }
    }
    ThemeUtils.java

    View Slide

  102. public class LaunchScreenActivity extends AppCompatActivity {


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    ThemeUtils.ensureRuntimeTheme(this);

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_launch_screen);

    // ...

    }


    }
    LaunchScreenActivity.java

    View Slide

  103. public class LaunchScreenActivity extends AppCompatActivity {


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    ThemeUtils.ensureRuntimeTheme(this);

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_launch_screen);

    // ...

    }


    }
    LaunchScreenActivity.java
    Inherit AppCompatActivity
    to get AppCompat behaviour
    public class LaunchScreenActivity extends AppCompatActivity {

    View Slide

  104. public class LaunchScreenActivity extends AppCompatActivity {


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    ThemeUtils.ensureRuntimeTheme(this);

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_launch_screen);

    // ...

    }


    }
    LaunchScreenActivity.java
    ThemeUtils.ensureRuntimeTheme(this); Dynamically switch
    to the runtime theme

    View Slide

  105. Don’t forget every screen is an
    entry point

    View Slide

  106. Consider launch screen as
    boring screen

    View Slide

  107. Don’t try to outsmart Android
    Embrace it!

    View Slide

  108. That’s all Folks!
    @cyrilmottier • cyrilmottier.com

    View Slide