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

What's new in Jetpack 2022

What's new in Jetpack 2022

I/O 22 Extended in Korea Android 행사에서 발표한 자료입니다.
https://festa.io/events/2339

Sungyong An

June 11, 2022
Tweet

More Decks by Sungyong An

Other Decks in Technology

Transcript

  1. What’s new in
    Jetpack
    #androidjetpack

    View Slide

  2. android

    jetpack

    View Slide

  3. 79%
    2021 using 2+ non-core Jetpack libraries
    90%
    2022
    of top 1K apps

    View Slide

  4. Room
    Lifecycle
    Activity
    DataStore
    Browser
    JankStats
    Baseline Profiles
    Macrobenchmark
    Tracing
    WindowManager
    DragAndDrop
    AppCompat Compose
    Annotation
    Fragment
    Media
    Core
    Navigation
    WorkManager
    SlidingPaneLayout
    SavedState
    RecyclerView
    Preference
    LocalBroadcastManager
    Paging
    Emoji2
    CustomView
    ConstraintLayout
    Camera
    Collection

    View Slide

  5. Unintentional dependency changes
    Android 12 Target

    View Slide

  6. View Slide

  7. Link: https://developer.android.com/distribute/play-policies

    View Slide

  8. Link: https://developer.android.com/google/play/requirements/target-sdk

    View Slide

  9. Link: https://developer.android.com/guide/components/intents-filters#DeclareMutabilityPendingIntent

    View Slide

  10. // Application Codes


    PendingIntent.getBroadcast(


    context,


    REQUEST_CODE,


    intent,


    - flags


    + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {


    + flags or PendingIntent.FLAG_IMMUTABLE


    + } else {


    + flags


    + }


    )

    View Slide

  11. Adobe Stock#243026154
    play-services-

    analytics 17.0.1
    WorkManager 2.7
    https://developers.google.com/android/guides/releases#august_19_2021 https://developer.android.com/jetpack/androidx/releases/work#version_27_2
    Library Updates

    View Slide

  12. // androidx.work:work-runtime:2.7.0


    // Unintentional dependency changes!!!


    androidx.annotation:annotation-experimental:1.0.0 -> 1.1.0


    androidx.core:core:1.5.0 -> 1.6.0


    + androidx.startup:startup-runtime:1.0.0


    + \--- androidx.tracing:tracing:1.0.0


    androidx.room:room-runtime:2.1.0 -> 2.2.5


    org.jetbrains.kotlin:kotlin-stdlib:1.4.32 -> 1.5.30


    org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32 -> 1.5.0


    org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3 -> 1.5.0

    View Slide

  13. // androidx.work:work-runtime:2.7.0


    // Unintentional dependency changes!!!


    androidx.annotation:annotation-experimental:1.0.0 -> 1.1.0


    androidx.core:core:1.5.0 -> 1.6.0


    + androidx.startup:startup-runtime:1.0.0


    + \--- androidx.tracing:tracing:1.0.0


    androidx.room:room-runtime:2.1.0 -> 2.2.5


    org.jetbrains.kotlin:kotlin-stdlib:1.4.32 -> 1.5.30


    org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32 -> 1.5.0


    org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3 -> 1.5.0
    How to
    prevent
    this?
    🤔

    View Slide

  14. Link: https://github.com/dropbox/dependency-guard

    View Slide

  15. // Example


    dependencies {


    - implementation "androidx.activity:activity-ktx:1.3.1"


    + implementation "androidx.activity:activity-ktx:1.4.0"


    }


    View Slide

  16. // Example


    dependencies {


    - implementation "androidx.activity:activity-ktx:1.3.1"


    + implementation "androidx.activity:activity-ktx:1.4.0"


    }


    -----------------------------------------------------------------------


    $ ./gradlew dependencyGuard


    > Task :app:dependencyGuard FAILED


    Dependencies Changed in :app for configuration releaseRuntimeClasspath


    - androidx.activity:activity-ktx:1.3.1


    + androidx.activity:activity-ktx:1.4.0


    - androidx.activity:activity:1.3.1


    + androidx.activity:activity:1.4.0

    View Slide

  17. Rewritten from Java to Kotlin
    Kotlin

    View Slide

  18. 18
    Support for Kotlin!
    Google I/O '17
    Link: https://android-developers.googleblog.com/2017/05/android-announces-support-for-kotlin.html

    View Slide

  19. 2011
    Android

    Support

    Library
    2017
    Architecture

    Components
    Kotlin
    Timeline
    ...
    Link: https://android-developers.googleblog.com/2017/11/announcing-architecture-components-10.html
    package helloWorld


    fun main(args: Array) {


    println("Hello World!")


    }

    View Slide

  20. 2011
    Android

    Support

    Library
    2017
    Architecture

    Components
    Kotlin
    2018
    Android

    Jetpack
    KTX
    Timeline
    ...
    Link: https://android-developers.googleblog.com/2018/02/introducing-android-ktx-even-sweeter.html
    // Kotlin


    val uri = Uri.parse(myUriString)


    // Kotlin with Android KTX


    val uri = myUriString.toUri()

    View Slide

  21. 2011
    Android

    Support

    Library
    2017
    Architecture

    Components
    Kotlin
    2019
    Kotlin first!
    Benchmark
    2018
    Android

    Jetpack
    KTX
    Timeline
    ...
    Link: https://android-developers.googleblog.com/2019/05/google-io-2019-empowering-developers.html
    Kotlin/Everywhere
    Many new Jetpack APIs and features

    will be offered first in Kotlin.

    View Slide

  22. 2011
    Android

    Support

    Library
    2017
    Architecture

    Components
    Kotlin
    2019
    Kotlin first!
    Benchmark
    2018
    Android

    Jetpack
    KTX
    2020
    Compose
    DataStore
    Window
    Paging 3
    Timeline
    ...
    Link: https://android-developers.googleblog.com/2020/07/getting-on-same-page-with-paging-3.html

    View Slide

  23. 2011
    Android

    Support

    Library
    2017
    Architecture

    Components
    Kotlin
    2019
    Kotlin first!
    Benchmark
    2018
    Android

    Jetpack
    KTX
    2020
    Compose
    DataStore
    Window
    Paging 3
    Timeline
    ...
    Link: https://developer.android.com/jetpack/androidx/releases/navigation#2.4.0
    2022
    Collection 1.3
    Annotation 1.4
    Navigation 2.4
    Fragment 1.5
    Activity 1.4
    Partially rewritten.

    View Slide

  24. // Navigation.java


    public final class Navigation {


    @NonNull public static View.OnClickListener createNavigateOnClickListener(


    @IdRes final int resId) {


    return createNavigateOnClickListener(resId, null);


    }


    @NonNull public static View.OnClickListener createNavigateOnClickListener(


    @IdRes final int resId,


    @Nullable final Bundle args) {


    return new View.OnClickListener() {


    @Override


    public void onClick(View view) {


    findNavController(view).navigate(resId, args);


    }


    };


    }


    }

    View Slide

  25. // Navigation.kt


    public object Navigation {


    @JvmStatic


    @JvmOverloads


    public fun createNavigateOnClickListener(


    @IdRes resId: Int,


    args: Bundle? = null


    ): View.OnClickListener {


    return View.OnClickListener { view ->


    findNavController(view).navigate(resId, args)


    }


    }


    }

    View Slide

  26. // api/current.txt


    public final class Navigation {


    - method public static android.view.View.OnClickListener


    createNavigateOnClickListener(@IdRes int);


    - method public static android.view.View.OnClickListener


    createNavigateOnClickListener(@IdRes int, android.os.Bundle?);


    + method public static android.view.View.OnClickListener


    createNavigateOnClickListener(@IdRes int resId);


    + method public static android.view.View.OnClickListener


    createNavigateOnClickListener(@IdRes int resId, optional android.os.Bundle? args);


    + field public static final androidx.navigation.Navigation INSTANCE;


    }
    Same APIs!

    -ktx is no longer required.

    View Slide

  27. 2011
    Android

    Support

    Library
    2017
    Architecture

    Components
    Kotlin
    2019
    Kotlin first!
    Benchmark
    2018
    Android

    Jetpack
    KTX
    2020
    Compose
    DataStore
    Window
    Paging 3
    Timeline
    2022
    Collection 1.3
    Annotation 1.4
    Navigation 2.4
    Fragment 1.5
    Activity 1.4
    2023 (?)
    SavedState 1.2
    Room 2.5
    Work 2.8
    ...
    ...

    View Slide

  28. // WorkManager


    // 2.7.1


    val constraints = Constraints.Builder()


    .setRequiresCharging(true)


    .setRequiresBatteryNotLow(true)


    .setRequiresDeviceIdle(true)


    .build()


    // 2.8.0-alpha02


    val constraints = Constraints(


    requiresCharging = true,


    requiresBatteryNotLow = true,


    requiresDeviceIdle = true,


    )


    Enhanced APIs

    for Kotlin users!

    View Slide

  29. Room, Navigation, and more…
    Architecture Components

    View Slide

  30. Room
    Link: https://developer.android.com/jetpack/androidx/releases/room#2.4.0
    Stable
    Auto Migrations

    Relational Query Methods

    Native support for Paging 3.0 (room-paging)

    Performance improvements with KSP
    androidx.room:room:2.4.2

    View Slide

  31. Link: https://developer.android.com/training/data-storage/room/migrating-db-versions#automated
    // Auto Migrations


    @Database(


    version = 3,


    entities = [User::class],


    autoMigrations = [


    AutoMigration(from = 1, to = 2, spec = AppDatabase.MyAutoMigration::class),


    AutoMigration(from = 2, to = 3)


    ]


    )


    abstract class AppDatabase : RoomDatabase() {


    @RenameTable(fromTableName = "User", toTableName = "AppUser")


    class MyAutoMigration : AutoMigrationSpec


    ...


    }

    View Slide

  32. Link: https://developer.android.com/training/data-storage/room/accessing-data#multimap
    // Supports multimap return types


    // like Map, SparseArray, LongSparseArray, etc.


    // 1:1 Relation Map


    @Query("SELECT * FROM Song JOIN Artist ON Song.artistId = Artist.artistId")


    fun getSongAndArtist(): Map


    // 1:N Relation Map


    @Query("SELECT * FROM Artist JOIN Album ON Artist.id = Album.artistId")


    fun getArtistAndAlbums(): Map>

    View Slide

  33. Paging
    Link: https://developer.android.com/jetpack/androidx/releases/paging#3.1.0
    Stable
    Stable support for Rx and Guava integrations

    Improvements to handling of invalid or stale data

    More comprehensive callbacks

    New introductory codelab for Paging 3
    androidx.paging:paging:3.1.1

    View Slide

  34. DataStore
    Link: https://developer.android.com/jetpack/androidx/releases/datastore#1.0.0
    Stable
    MAD Skills: DataStore

    goo.gle/mad-skills-datastore

    How to migrate from SharedPreferences to Proto DataStore.

    d.android.com/codelabs/android-proto-datastore#7
    androidx.datastore:datastore:1.0.0

    View Slide

  35. Lifecycle
    Deprecated @OnLifecycleEvent

    Integration between ViewModel and Jetpack Compose

    Restartable Lifecycle-aware coroutines
    androidx.lifecycle:lifecycle:2.4.1
    androidx.lifecycle:lifecycle:2.5.0+
    ViewModel CreationExtras
    Link: https://developer.android.com/jetpack/androidx/releases/lifecycle
    Stable / RC

    View Slide

  36. Link: https://developer.android.com/training/data-storage/room/accessing-data#multimap
    // Use DefaultLifecycleObserver or LifecycleEventObserver instead.


    - class MyObserver(...) : LifecycleObserver {


    + class MyObserver(...) : DefaultLifecycleObserver {


    - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)


    fun onResume() { ... }


    - @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)


    fun onPause() { ... }


    }
    Stable

    View Slide

  37. Link: https://developer.android.com/topic/libraries/architecture/coroutines#restart
    // Restartable Lifecycle-aware coroutines


    viewLifecycleOwner.lifecycleScope.launch {


    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {


    viewModel.someDataFlow.collect {


    // Process item


    }


    }


    }


    viewLifecycleOwner.lifecycleScope.launch {


    exampleProvider.exampleFlow()


    .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)


    .collect {


    // Process the value.


    }


    }
    Stable

    View Slide

  38. // current


    class MyActivity : AppCompatActivity() {


    private val viewModel: MyViewModel by viewModels()


    ...


    }


    class MyViewModel(


    private val savedStateHandle: SavedStateHandle,


    ) : ViewModel() {


    fun loadData(id: String) {


    ...


    }


    }
    RC

    View Slide

  39. // current


    class MyActivity : AppCompatActivity() {


    private val viewModel: MyViewModel by viewModels()


    override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {


    return super.getDefaultViewModelProviderFactory() // SavedStateViewModelFactory


    }


    }


    class MyViewModel(


    private val savedStateHandle: SavedStateHandle,


    ) : ViewModel() {


    fun loadData(id: String) {


    ...


    }


    }
    RC

    View Slide

  40. class MyActivity : AppCompatActivity() {


    private val viewModel: MyViewModel by viewModels()


    }


    class MyViewModel(


    private val savedStateHandle: SavedStateHandle,


    + private val id: String,


    ) : ViewModel() {


    - fun loadData(id: String) {


    - ...


    - }


    }
    RC

    View Slide

  41. Link: https://pluu.github.io/blog/android/2022/03/12/creationextras/
    class MyActivity : AppCompatActivity() {


    + private val key = object : CreationExtras.Key {}


    private val factory = object : ViewModelProvider.Factory {


    @Suppress("UNCHECKED_CAST")


    override fun create(


    modelClass: Class,


    + extras: CreationExtras,


    ): T {


    val savedStateHandle = extras.createSavedStateHandle()


    + val id = extras[key].orEmpty()


    return MainViewModel(


    savedStateHandle = savedStateHandle,


    + id = id,


    ) as T


    }


    }


    override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory = factory


    ...
    RC

    View Slide

  42. Navigation
    Link: https://d.android.com/jetpack/androidx/releases/navigation#2.4.0
    Stable
    Support for two-pane layouts

    Integration with Jetpack Compose

    Support for Multiple Back Stacks
    androidx.navigation:navigation:2.4.2

    View Slide

  43. Phone Tablet
    Link: https://github.com/CesarValiente/navigation-foldable

    View Slide

  44. Link: https://github.com/CesarValiente/navigation-foldable
    class NavigationActivity : AppCompatActivity(R.layout.activity_navigation)







    android:id="@+id/nav_host_fragment"


    android:name="androidx.navigation.fragment.NavHostFragment"


    app:navGraph="@navigation/main_nav_graph"


    app:defaultNavHost="true"


    ... />










    android:id="@+id/two_pane"


    android:name="com.example.navigation.TwoPaneFragment" />



    View Slide

  45. Link: https://github.com/CesarValiente/navigation-foldable
    class TwoPaneFragment : AbstractListDetailFragment() {


    override fun onCreateListPaneView(


    inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?


    ): View {


    return ListPaneBinding.inflate(inflater, container, false)


    }


    override fun onListPaneViewCreated(view: View, savedInstanceState: Bundle?) {


    oneFragmentButton.setOnClickListener {


    openDetails(R.id.one_fragment)


    }


    }


    ...


    }





    android:text="Fragment ONE" />

    View Slide

  46. Link: https://github.com/CesarValiente/navigation-foldable
    class TwoPaneFragment : AbstractListDetailFragment() {


    ...


    private fun openDetails(destinationId: Int) {


    val detailNavController = detailPaneNavHostFragment.navController


    detailNavController.navigate(


    resId = destinationId,


    args = null,


    navOptions = navOptions {


    popUpTo(detailNavController.graph.startDestinationId) { inclusive = true }


    }


    )


    slidingPaneLayout.open()


    }


    override fun onCreateDetailPaneNavHostFragment(): NavHostFragment {


    return NavHostFragment.create(R.navigation.detail_pane_nav_graph)


    }


    }

    View Slide

  47. Link: https://github.com/CesarValiente/navigation-foldable








    android:id="@+id/one_fragment"


    android:name="com.example.navigation.OneFragment" />




    android:id="@+id/two_fragment"


    android:name="com.example.navigation.TwoFragment" />




    android:id="@+id/three_fragment"


    android:name="com.example.navigation.ThreeFragment" />





    class OneFragment : Fragment(R.layout.one_fragment)


    class TwoFragment : Fragment(R.layout.two_fragment)


    class ThreeFragment : Fragment(R.layout.three_fragment)

    View Slide

  48. Large Screen, and more...
    User Interface

    View Slide

  49. SlidingPaneLayout
    Link: https://d.android.com/jetpack/androidx/releases/slidingpanelayout#1.2.0
    Stable
    Support for two-pane UX
    androidx.slidingpanelayout:


    slidingpanelayout:1.2.0

    View Slide



  50. android:id="@+id/sliding_pane_layout">







    android:id="@+id/list_pane"


    android:layout_width="280dp"


    android:layout_height="match_parent"


    android:layout_gravity="start" />







    android:id="@+id/detail_container"


    android:layout_width="300dp"


    android:layout_weight="1"


    android:layout_height="match_parent"


    android:name="com.example.SelectAnItemFragment" />



    Link: https://developer.android.com/guide/topics/ui/layout/twopane

    View Slide



  51. android:id="@+id/sliding_pane_layout">







    android:id="@+id/list_pane"


    android:layout_width="280dp"


    android:layout_height="match_parent"


    android:layout_gravity="start" />







    android:id="@+id/detail_container"


    android:layout_width="300dp"


    android:layout_weight="1"


    android:layout_height="match_parent"


    android:name="com.example.SelectAnItemFragment" />



    Link: https://developer.android.com/guide/topics/ui/layout/twopane

    View Slide

  52. Link: https://developer.android.com/guide/topics/ui/layout/twopane
    // A method on the Fragment that owns the SlidingPaneLayout,


    // called by the adapter when an item is selected.


    fun openDetails(itemId: Int) {


    childFragmentManager.commit {


    setReorderingAllowed(true)


    replace(R.id.detail_container, bundleOf("itemId" to itemId))


    // If we're already open and the detail pane is visible,


    // crossfade between the fragments.


    if (binding.slidingPaneLayout.isOpen) {


    setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)


    }


    }


    binding.slidingPaneLayout.open()


    }

    View Slide

  53. WindowManager
    Link: https://developer.android.com/jetpack/androidx/releases/window#1.0.0
    Stable
    Supports foldable phones to adjust how content is displayed

    Smart layout avoids placing content in occluded areas

    Already integrated into SlidingPaneLayout
    androidx.window:window:1.0.0

    View Slide

  54. Preference
    Link: https://d.android.com/jetpack/androidx/releases/preference#1.2.0
    Stable
    Support for two-pane preference
    androidx.preference:preference:1.2.0

    View Slide

  55. Phone Tablet
    Link: https://github.com/CesarValiente/preferences-foldable

    View Slide

  56. Link: https://github.com/CesarValiente/preferences-foldable
    class PreferenceActivity : AppCompatActivity(R.layout.activity_preference)







    android:name="com.example.preferences.TwoPanePreference"


    ... />


    class TwoPanePreference : PreferenceHeaderFragmentCompat() {


    override fun onCreatePreferenceHeader(): PreferenceFragmentCompat {


    return HeaderPreference()


    }


    }


    class HeaderPreference : PreferenceFragmentCompat() {


    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {


    setPreferencesFromResource(R.xml.preferences, rootKey)


    }


    }

    View Slide









  57. android:fragment="com.example.preferences.OneFragment"


    android:key="pref_one"


    ... />




    android:fragment="com.example.preferences.TwoFragment"


    android:key="pref_two"


    ... />




    android:fragment="com.example.preferences.ThreeFragment"


    android:key="pref_three"


    ... />



    Link: https://github.com/CesarValiente/preferences-foldable

    View Slide

  58. DragAndDrop
    Link: https://developer.android.com/jetpack/androidx/releases/draganddrop#1.0.0
    Stable
    Simplifies ingesting dropped data

    Drop target highlighting
    androidx.draganddrop:draganddrop:1.0.0

    View Slide

  59. Link: https://developer.android.com/guide/topics/ui/drag-drop

    View Slide

  60. Link: https://github.com/android/user-interface-samples/tree/main/DragAndDrop
    DragStartHelper(binding.textDragItem) { view, _ ->


    val text = (view as TextView).text


    // Create the ClipData to be shared


    val dragClipData = ClipData.newPlainText(/*label*/"Text", text)


    // Use the default drag shadow


    val dragShadowBuilder = View.DragShadowBuilder(view)


    // Initiate the drag. Note the DRAG_FLAG_GLOBAL,


    // which allows for drag events to be listened to by apps other than the source app.


    view.startDragAndDrop(dragClipData, dragShadowBuilder, null, DRAG_FLAG_GLOBAL)


    }.attach()

    View Slide

  61. Link: https://github.com/android/user-interface-samples/tree/main/DragAndDrop
    DropHelper.configureView(


    /* activity */ this, binding.textDropTarget,


    /* mimeTypes */ arrayOf(


    ClipDescription.MIMETYPE_TEXT_PLAIN,


    "application/x-arc-uri-list" // Support external items on Chrome OS Android 9


    ),


    DropHelper.Options.Builder()


    .setHighlightColor(getColor(R.color.purple_300))


    .build()


    ) { view, payload ->


    resetDropTarget()


    val item = payload.clip.getItemAt(0)


    val (_, remaining) = payload.partition { it == item }


    if (payload.clip.description.hasMimeType(MIMETYPE_TEXT_PLAIN) {


    binding.textDropTarget.text = item.text


    }


    // Allow the system to handle any remaining ClipData.Item objects if applicable


    remaining


    }

    View Slide

  62. Fragment
    Stable
    New state manager only

    Support multiple back stacks

    Support FragmentStrictMode
    androidx.fragment:fragment:1.4.1

    View Slide

  63. Link: https://medium.com/androiddevelopers/fragments-rebuilding-the-internals-61913f8bf48e
    Old New
    // Fragment 1.3


    FragmentManager.enableNewStateManager(false)
    // Fragment 1.4


    FragmentManager.enableNewStateManager(false)

    View Slide

  64. Link: https://developer.android.com/guide/fragments/debugging
    FragmentStrictMode.defaultPolicy =


    FragmentStrictMode.Policy.Builder()


    .detectFragmentReuse()


    .detectFragmentTagUsage()


    .detectRetainInstanceUsage()


    .detectSetUserVisibleHint()


    .detectTargetFragmentUsage()


    .detectWrongFragmentContainer()


    .allowViolation(MyFragment::class.java, SetUserVisibleHintViolation::class.java)


    .apply {


    if (BuildConfig.DEBUG) {


    // Fail early on DEBUG builds


    penaltyDeath()


    } else {


    // Log to Crashlytics on RELEASE builds


    penaltyListener { FirebaseCrashlytics.getInstance().recordException(it) }


    }


    }


    .build()

    View Slide

  65. AppCompat
    Stable / Alpha
    Emoji2 integration
    androidx.appcompat:appcompat:1.4.2
    androidx.appcompat:appcompat:1.6.0+
    Support for custom app locales
    🫠🧑🦰🦩
    ☐☐☐
    Link: https://developer.android.com/guide/topics/ui/look-and-feel/emoji2

    View Slide

  66. // Get app locales


    val locales: LocaleListCompat =


    AppCompatDelegate.getApplicationLocales()


    // Set app locales


    val locales: LocaleListCompat =


    LocaleListCompat.forLanguageTags("xx-YY")


    // or LocaleListCompat.create(Locale.KOREA)


    AppCompatDelegate.setApplicationLocales(locales)
    Link: https://developer.android.com/about/versions/13/features/app-languages#androidx-impl

    View Slide

  67. override fun attachBaseContext(newBase: Context) {


    if (SDK_INT < 33) {


    // Preferred locales are managed automatically on API level 33+,


    // but we need to load them manually on earlier platform versions.


    val locales = loadLocalesFromPreferences()


    AppCompatDelegate.setApplicationLocales(locales)


    }


    super.attachBaseContext(newBase)


    }
    Link: https://developer.android.com/about/versions/13/features/app-languages#androidx-impl

    View Slide

  68. override fun attachBaseContext(newBase: Context) {


    if (SDK_INT < 33) {


    // Preferred locales are managed automatically on API level 33+,


    // but we need to load them manually on earlier platform versions.


    val locales = loadLocalesFromPreferences()


    AppCompatDelegate.setApplicationLocales(locales)


    }


    super.attachBaseContext(newBase)


    }




    android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"


    android:enabled="false"


    android:exported="false">




    android:name="autoStoreLocales"


    android:value="true" />



    Link: https://developer.android.com/about/versions/13/features/app-languages#androidx-impl

    View Slide

  69. ConstraintLayout
    Alpha
    Grid helper
    androidx.constraintlayout:constraintlayout:2.2.0+
    Link: https://github.com/androidx/constraintlayout/wiki/What's-New-in-2.2

    View Slide






  70. android:text="C" />


    ...




    android:id="@+id/calculatorText"


    android:background="@color/cardview_shadow_start_color"


    android:text="100"


    android:gravity="bottom|right" />




    android:text="0" />


    ...



    View Slide






  71. android:id="@+id/grid"


    android:layout_width="match_parent"


    android:layout_height="match_parent"


    app:constraint_referenced_ids="


    calculatorText,btn0,


    clear,neg,percent,div,btn7,btn8,btn9,mult,btn4,


    btn5,btn6,sub,btn1,btn2,btn3,plus,btn0,dot,equal"


    app:grid_columns="4"


    app:grid_rows="7"


    app:grid_horizontalGaps="5dp"


    app:grid_verticalGaps="5dp"


    app:grid_orientation="horizontal"


    app:grid_spans="0:2x4,24:1x2" />



    View Slide






  72. android:id="@+id/grid"


    android:layout_width="match_parent"


    android:layout_height="match_parent"


    app:constraint_referenced_ids="


    calculatorText,btn0,


    clear,neg,percent,div,btn7,btn8,btn9,mult,btn4,


    btn5,btn6,sub,btn1,btn2,btn3,plus,btn0,dot,equal"


    app:grid_columns="4"


    app:grid_rows="7"


    app:grid_horizontalGaps="5dp"


    app:grid_verticalGaps="5dp"


    app:grid_orientation="horizontal"


    app:grid_spans="0:2x4,24:1x2" />



    9
    8 10 11
    13
    12 14 15
    17
    16 18 19
    21
    20 22 23
    25
    24 26 27
    5
    4 6 7
    1
    0 2 3

    View Slide






  73. android:id="@+id/grid"


    android:layout_width="match_parent"


    android:layout_height="match_parent"


    app:constraint_referenced_ids="


    calculatorText,btn0,


    clear,neg,percent,div,btn7,btn8,btn9,mult,btn4,


    btn5,btn6,sub,btn1,btn2,btn3,plus,btn0,dot,equal"


    app:grid_columns="4"


    app:grid_rows="7"


    app:grid_horizontalGaps="5dp"


    app:grid_verticalGaps="5dp"


    app:grid_orientation="horizontal"


    app:grid_spans="0:2x4,24:1x2" />



    5
    4 6 7
    1
    0 2 3

    View Slide






  74. android:id="@+id/grid"


    android:layout_width="match_parent"


    android:layout_height="match_parent"


    app:constraint_referenced_ids="


    calculatorText,btn0,


    clear,neg,percent,div,btn7,btn8,btn9,mult,btn4,


    btn5,btn6,sub,btn1,btn2,btn3,plus,btn0,dot,equal"


    app:grid_columns="4"


    app:grid_rows="7"


    app:grid_horizontalGaps="5dp"


    app:grid_verticalGaps="5dp"


    app:grid_orientation="horizontal"


    app:grid_spans="0:2x4,24:1x2" />



    25
    24

    View Slide

  75. Now in 1.2 beta!
    Compose

    View Slide

  76. Nested Scrolling
    Interop
    Link: https://d.android.com/jetpack/compose/interop/compose-in-existing-ui#nested-scrolling
    Compose 1.2
    androidx.compose.ui:ui:1.2.0-alpha08 +
    Lazy

    Layouts
    Scrolling

    View
    Compose
    AppBar

    Layout

    View Slide

  77. Scrolling

    Compose

    in View
    Lazy

    Layouts
    AppBar

    Layout

    View Slide
























  78. android:id="@+id/compose_view"


    app:layout_behavior="@string/appbar_scrolling_view_behavior" />






    Link: Nested Scrolling Interop Phase 1: Scrolling compose in cooperating view (I5d1ac)

    View Slide

  79. @ExperimentalComposeUiApi


    class ComposeInAndroidCoordinatorLayout : ComponentActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {


    super.onCreate(savedInstanceState)


    setContentView(R.layout.compose_in_android_coordinator_layout)


    findViewById(R.id.compose_view).apply {


    setContent {


    val nestedScrollInterop = rememberNestedScrollInteropConnection(this)


    LazyColumn(modifier = Modifier.nestedScroll(nestedScrollInterop)) {


    items(20) { item ->


    Box(...) { Text(item.toString()) }


    }


    }


    }


    }


    }


    }
    Link: Nested Scrolling Interop Phase 1: Scrolling compose in cooperating view (I5d1ac)

    View Slide

  80. Scrolling

    View

    in Compose
    Scrolling

    View
    Compose

    View Slide

  81. View Slide

  82. val ToolbarHeight = 48.dp


    @Composable


    private fun NestedScrollInteropComposeParentWithAndroidChild() {


    val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() }


    val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }


    val nestedScrollConnection = remember {


    object : NestedScrollConnection {


    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {


    val delta = available.y


    val newOffset = toolbarOffsetHeightPx.value + delta


    toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)


    return Offset.Zero


    }


    }


    }


    ...
    Link: Nested Scrolling Interop Phase 2: A scrolling view child inside a compose parent (If7949)

    View Slide

  83. @Composable


    private fun NestedScrollInteropComposeParentWithAndroidChild() {


    ...


    Box(


    Modifier


    .fillMaxSize()


    .nestedScroll(nestedScrollConnection)


    ) {


    TopAppBar(


    modifier = Modifier


    .height(ToolbarHeight)


    .offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) },


    title = { Text("toolbar offset is ${toolbarOffsetHeightPx.value}") }


    )


    ...
    Link: Nested Scrolling Interop Phase 2: A scrolling view child inside a compose parent (If7949)

    View Slide

  84. @Composable


    private fun NestedScrollInteropComposeParentWithAndroidChild() {


    ...


    AndroidView(


    { context -> LayoutInflater.from(context)


    .inflate(R.layout.android_in_compose_nested_scroll_interop, null).apply {


    with(findViewById(R.id.main_list)) {


    layoutManager = LinearLayoutManager(context, VERTICAL, false)


    adapter = NestedScrollInteropAdapter()


    }


    }.also {


    ViewCompat.setNestedScrollingEnabled(it, true)


    }


    },


    modifier = Modifier.padding(top = ToolbarHeight).fillMaxWidth(),


    )


    }


    }
    Link: Nested Scrolling Interop Phase 2: A scrolling view child inside a compose parent (If7949)

    View Slide

  85. Compose in
    RecyclerView
    Link: https://d.android.com/jetpack/compose/interop/compose-in-existing-ui#compose-recyclerview
    Compose 1.2
    androidx.compose.ui:ui:1.2.0-beta02 +


    androidx.recyclerview:


    recyclerview:1.3.0-alpha02 +

    View Slide

  86. // Compose 1.1 + RecyclerView 1.2


    import androidx.compose.ui.platform.ComposeView


    class MyComposeAdapter : RecyclerView.Adapter() {


    override fun onCreateViewHolder(...): MyComposeViewHolder {


    return MyComposeViewHolder(ComposeView(parent.context))


    }


    override fun onViewRecycled(holder: MyComposeViewHolder) {


    + holder.composeView.disposeComposition()


    }


    }


    class MyComposeViewHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView) {


    init {


    + composeView.setViewCompositionStrategy(


    + ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed


    + )


    }


    }
    Link: https://developer.android.com/jetpack/compose/interop/compose-in-existing-ui#compose-recyclerview

    View Slide

  87. // Compose 1.2 + RecyclerView 1.3


    import androidx.compose.ui.platform.ComposeView


    class MyComposeAdapter : RecyclerView.Adapter() {


    override fun onCreateViewHolder(...): MyComposeViewHolder {


    return MyComposeViewHolder(ComposeView(parent.context))


    }


    // override fun onViewRecycled(holder: MyComposeViewHolder) {


    // holder.composeView.disposeComposition()


    // }


    }


    class MyComposeViewHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView) {


    // init {


    // composeView.setViewCompositionStrategy(


    // ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed


    // )


    // }


    }

    View Slide

  88. CustomView-PoolingContainer
    Link: https://cs.android.com/.../androidx-customview-poolingcontainer-documentation.md

    View Slide

  89. abstract class AbstractComposeView(...) : ViewGroup(...) {


    private var disposeViewCompositionStrategy: (() -> Unit)? =


    - ViewCompositionStrategy.DisposeOnDetachedFromWindow.installFor(this)


    + ViewCompositionStrategy.Default.installFor(this)


    }


    class ComposeView(...) : AbstractComposeView(...) { ... }


    interface ViewCompositionStrategy {


    companion object {


    + val Default: ViewCompositionStrategy


    + get() = DisposeOnDetachedFromWindowIfNotInPoolingContainer


    }


    ...


    }
    Link: Integrate PoolingContainer into Compose UI(If7282)

    View Slide

  90. + object DisposeOnDetachedFromWindowIfNotInPoolingContainer : ViewCompositionStrategy {


    + override fun installFor(view: AbstractComposeView): () -> Unit {


    + val listener = object : View.OnAttachStateChangeListener {


    + override fun onViewAttachedToWindow(v: View) {}


    + override fun onViewDetachedFromWindow(v: View) {


    + if (!view.isWithinPoolingContainer) {


    + view.disposeComposition()


    + }


    + }


    + }


    + view.addOnAttachStateChangeListener(listener)


    + val poolingContainerListener = PoolingContainerListener { view.disposeComposition() }


    + view.addPoolingContainerListener(poolingContainerListener)


    + return {


    + view.removeOnAttachStateChangeListener(listener)


    + view.removePoolingContainerListener(poolingContainerListener)


    + }


    + }


    + }
    Link: Integrate PoolingContainer into Compose UI(If7282)

    View Slide

  91. + object DisposeOnDetachedFromWindowIfNotInPoolingContainer : ViewCompositionStrategy {


    + override fun installFor(view: AbstractComposeView): () -> Unit {


    + val listener = object : View.OnAttachStateChangeListener {


    + override fun onViewAttachedToWindow(v: View) {}


    + override fun onViewDetachedFromWindow(v: View) {


    + if (!view.isWithinPoolingContainer) {


    + view.disposeComposition()


    + }


    + }


    + }


    + view.addOnAttachStateChangeListener(listener)


    + val poolingContainerListener = PoolingContainerListener { view.disposeComposition() }


    + view.addPoolingContainerListener(poolingContainerListener)


    + return {


    + view.removeOnAttachStateChangeListener(listener)


    + view.removePoolingContainerListener(poolingContainerListener)


    + }


    + }


    + }
    Link: Integrate PoolingContainer into Compose UI(If7282)

    View Slide

  92. + object DisposeOnDetachedFromWindowIfNotInPoolingContainer : ViewCompositionStrategy {


    + override fun installFor(view: AbstractComposeView): () -> Unit {


    + val listener = object : View.OnAttachStateChangeListener {


    + override fun onViewAttachedToWindow(v: View) {}


    + override fun onViewDetachedFromWindow(v: View) {


    + if (!view.isWithinPoolingContainer) {


    + view.disposeComposition()


    + }


    + }


    + }


    + view.addOnAttachStateChangeListener(listener)


    + val poolingContainerListener = PoolingContainerListener { view.disposeComposition() }


    + view.addPoolingContainerListener(poolingContainerListener)


    + return {


    + view.removeOnAttachStateChangeListener(listener)


    + view.removePoolingContainerListener(poolingContainerListener)


    + }


    + }


    + }
    Link: Integrate PoolingContainer into Compose UI(If7282)

    View Slide

  93. public class RecyclerView extends ViewGroup implements ... {


    public RecyclerView(...) {


    ...


    + PoolingContainer.setPoolingContainer(this, true);


    }


    public final class Recycler {


    ...


    void recycleViewHolderInternal(ViewHolder holder) {


    ...


    mViewInfoStore.removeViewHolder(holder);


    if (!cached && !recycled && transientStatePreventsRecycling) {


    + PoolingContainer.callPoolingContainerOnRelease(holder.itemView);


    ...


    }


    }


    }


    }
    Link: Integrate PoolingContainer into RecyclerView(Ib89d2)

    View Slide

  94. Link: https://cs.android.com/.../androidx-main:customview/customview-poolingcontainer/.../PoolingContainer.kt
    See the code

    for details. 👀

    View Slide

  95. Lazy Layouts
    Link: https://developer.android.com/jetpack/compose/lists
    Compose 1.2
    androidx.compose.foundation:foundation


    Lazy Layouts in Compose

    View Slide

  96. @OptIn(ExperimentalFoundationApi::class)


    LazyVerticalGrid(


    columns = GridCells.Fixed(2),


    verticalArrangement =


    Arragement.spaceBy(16.dp),


    horizontalArrangement =


    Arragement.spaceBy(16.dp),


    ) {


    items(data) { item ->


    Item(item)


    }


    }

    View Slide

  97. // New!


    LazyHorizontalGrid(


    rows = GridCells.Fixed(2),


    verticalArrangement =


    Arragement.spaceBy(16.dp),


    horizontalArrangement =


    Arragement.spaceBy(16.dp),


    ) {


    items(data) { item ->


    Item(item)


    }


    }

    View Slide

  98. instead of

    androidx.recyclerview.widget.


    StaggeredGridLayoutManager
    Not Available
    Staggered Grids
    Link: https://issuetracker.google.com/issues/182882362

    View Slide

  99. Adobe Stock#243026154
    Downloadable
    Fonts
    Link: https://developer.android.com/jetpack/compose/text#downloadable-fonts
    Compose 1.2
    androidx.compose.ui:ui-text-google-fonts

    View Slide

  100. val provider = GoogleFont.Provider(


    providerAuthority = "com.google.android.gms.fonts",


    providerPackage = "com.google.android.gms",


    certificates = R.array.com_google_android_gms_fonts_certs


    )


    val MontserratFont = GoogleFont(name = "Montserrat")


    val MontserratFontFamily = FontFamily(


    Font(googleFont = MontserratFont, fontProvider = provider)


    )


    Text(


    fontFamily = MontserratFontFamily,


    text = "Hello World!"


    )
    Link: https://github.com/android/compose-samples/pull/787

    View Slide

  101. Text Magnifier
    Link: I30b38, b/139320979
    Compose 1.2
    androidx.compose.foundation:foundation

    View Slide

  102. Show Layout
    Bounds

    immediately
    Link: I843d5, b/225937688
    Compose 1.2
    androidx.compose.ui:ui:1.2.0-beta03

    View Slide

  103. Recommended!
    Performance best practices for Jetpack Compose

    View Slide

  104. Compose for Wear OS
    Jetpack Compose

    for Wear OS
    Now in beta!
    Link: g.co/wear/compose

    View Slide

  105. Navigation
    androidx.wear.compose.navigation


    instead of

    androidx.navigation:navigation-compose
    Compose for Wear OS
    val navController =


    rememeberSwipeDismissableNavController()


    SwipeDismissableNavHost(


    navController = navController,


    startDestination = Screen.MainScreen.route


    ) {


    composable(route = Screen.MainScreen.route) {


    MyListScreen(...)


    }


    ...


    }

    View Slide

  106. ScalingLazyColumn
    androidx.wear.compose.material


    instead of

    androidx.compose.material:material
    Compose for Wear OS
    val scalingLazyListState =


    rememberScalingLazyListState()


    ScalingLazyColumn(


    state = scalingLazyListState,


    autoCentering = AutoCenteringParams()


    ) {


    item { ListHeader { Text("Title") } }


    items(messages.size) { message ->


    Card(/*...*/) { /*...*/ }


    }


    }

    View Slide

  107. androidx.wear.compose.material
    Compose for Wear OS
    Picker Stepper
    InlineSlider

    View Slide

  108. androidx.wear.compose.material
    Compose for Wear OS
    Dialog
    Circular

    ProgressIndicator
    Horizontal

    PageIndicator

    View Slide

  109. Horologist
    Wear OS ѐߊ੗ܳ ࠁ৮ೞח Ѫਸ

    ݾ಴۽ ೞח ۄ੉࠳۞ܻ ޘ਺ੑפ׮.

    ѐߊ੗о ੌ߈੸ਵ۽ ೙ਃ۽ ೞ૑݅

    ই૒ ࢎਊೡ ࣻ হח ӝמਸ ઁҕ೤פ׮.

    • Media

    • Composables

    • Compose Layout

    • Audio and UI

    • Tiles
    Compose for Wear OS
    Link: https://github.com/google/horologist

    View Slide

  110. Health Services
    Link: https://developer.android.com/training/wearables/health-services
    Now in alpha!
    Developing

    Health and

    Fitness Apps

    on Android

    View Slide

  111. Jetpackਸ ѐࢶೞب۾, ੉गܳ ١۾೧઱ࣁਃ.
    👆

    View Slide

  112. э਷ ੉गо ੉޷ ١۾غয ੓ਵݶ, Starܳ ׂ۞઱ࣁਃ.
    👆

    View Slide

  113. Thank you!

    Android Developer / NAVER WEBTOON

    Android GDE

    @fornewid
    d.android.com/jetpack

    speakerdeck.com/fornewid/
    whats-new-in-jetpack-2022
    Resources
    Sungyong An

    View Slide