Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. 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
  2. // 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 + } )
  3. // 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
  4. // 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? 🤔
  5. // 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
  6. 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!") }
  7. 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()
  8. 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.
  9. 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
  10. 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.
  11. // 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); } }; } }
  12. // 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) } } }
  13. // 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.
  14. 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 ... ...
  15. // 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!
  16. 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
  17. 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 ... }
  18. 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<Song, Artist> // 1:N Relation Map @Query("SELECT * FROM Artist JOIN Album ON Artist.id = Album.artistId") fun getArtistAndAlbums(): Map<Artist, List<Album>>
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. // current class MyActivity : AppCompatActivity() { private val viewModel:

    MyViewModel by viewModels() ... } class MyViewModel( private val savedStateHandle: SavedStateHandle, ) : ViewModel() { fun loadData(id: String) { ... } } RC
  25. // 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
  26. 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
  27. Link: https://pluu.github.io/blog/android/2022/03/12/creationextras/ class MyActivity : AppCompatActivity() { + private val

    key = object : CreationExtras.Key<String> {} private val factory = object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create( modelClass: Class<T>, + 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
  28. Link: https://github.com/CesarValiente/navigation-foldable class NavigationActivity : AppCompatActivity(R.layout.activity_navigation) <!-- res/layout/activity_navigation.xml --> <androidx.fragment.app.FragmentContainerView

    android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" app:navGraph="@navigation/main_nav_graph" app:defaultNavHost="true" ... /> <!-- res/navigation/main_nav_graph.xml --> <navigation app:startDestination="@+id/two_pane"> <fragment android:id="@+id/two_pane" android:name="com.example.navigation.TwoPaneFragment" /> </navigation>
  29. 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) } } ... } <!-- res/layout/list_pane.xml --> <Button android:id="@+id/one_fragment_button" android:text="Fragment ONE" />
  30. 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) } }
  31. Link: https://github.com/CesarValiente/navigation-foldable <!-- res/navigation/detail_pane_nav_graph.xml --> <navigation app:startDestination="@+id/one_fragment"> <fragment android:id="@+id/one_fragment" android:name="com.example.navigation.OneFragment"

    /> <fragment android:id="@+id/two_fragment" android:name="com.example.navigation.TwoFragment" /> <fragment android:id="@+id/three_fragment" android:name="com.example.navigation.ThreeFragment" /> </navigation> class OneFragment : Fragment(R.layout.one_fragment) class TwoFragment : Fragment(R.layout.two_fragment) class ThreeFragment : Fragment(R.layout.three_fragment)
  32. <androidx.slidingpanelayout.widget.SlidingPaneLayout android:id="@+id/sliding_pane_layout"> <!-- List Pane (Left) --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/list_pane" android:layout_width="280dp"

    android:layout_height="match_parent" android:layout_gravity="start" /> <!-- Detail Pane (Right) --> <androidx.fragment.app.FragmentContainerView android:id="@+id/detail_container" android:layout_width="300dp" android:layout_weight="1" android:layout_height="match_parent" android:name="com.example.SelectAnItemFragment" /> </androidx.slidingpanelayout.widget.SlidingPaneLayout> Link: https://developer.android.com/guide/topics/ui/layout/twopane
  33. <androidx.slidingpanelayout.widget.SlidingPaneLayout android:id="@+id/sliding_pane_layout"> <!-- List Pane (Left) --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/list_pane" android:layout_width="280dp"

    android:layout_height="match_parent" android:layout_gravity="start" /> <!-- Detail Pane (Right) --> <androidx.fragment.app.FragmentContainerView android:id="@+id/detail_container" android:layout_width="300dp" android:layout_weight="1" android:layout_height="match_parent" android:name="com.example.SelectAnItemFragment" /> </androidx.slidingpanelayout.widget.SlidingPaneLayout> Link: https://developer.android.com/guide/topics/ui/layout/twopane
  34. 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<ItemFragment>(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() }
  35. 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
  36. Link: https://github.com/CesarValiente/preferences-foldable class PreferenceActivity : AppCompatActivity(R.layout.activity_preference) <!-- res/layout/activity_preference.xml --> <androidx.fragment.app.FragmentContainerView

    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) } }
  37. <!-- res/xml/preference.xml --> <PreferenceScreen> <Preference android:fragment="com.example.preferences.OneFragment" android:key="pref_one" ... /> <Preference

    android:fragment="com.example.preferences.TwoFragment" android:key="pref_two" ... /> <Preference android:fragment="com.example.preferences.ThreeFragment" android:key="pref_three" ... /> </PreferenceScreen> Link: https://github.com/CesarValiente/preferences-foldable
  38. 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()
  39. 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 }
  40. Fragment Stable New state manager only Support multiple back stacks

    Support FragmentStrictMode androidx.fragment:fragment:1.4.1
  41. 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()
  42. 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
  43. // 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
  44. 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
  45. 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) } <service android:name="androidx.appcompat.app.AppLocalesMetadataHolderService" android:enabled="false" android:exported="false"> <meta-data android:name="autoStoreLocales" android:value="true" /> </service> Link: https://developer.android.com/about/versions/13/features/app-languages#androidx-impl
  46. <androidx.constraintlayout.widget.ConstraintLayout> <androidx.constraintlayout.helper.widget.Grid 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" /> </androidx.constraintlayout.widget.ConstraintLayout> 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
  47. <androidx.coordinatorlayout.widget.CoordinatorLayout> <com.google.android.material.appbar.AppBarLayout> <com.google.android.material.appbar.CollapsingToolbarLayout> <androidx.appcompat.widget.AppCompatImageView /> <androidx.appcompat.widget.Toolbar /> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView

    android:id="@+id/compose_view" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <com.google.android.material.floatingactionbutton.FloatingActionButton /> </androidx.coordinatorlayout.widget.CoordinatorLayout> Link: Nested Scrolling Interop Phase 1: Scrolling compose in cooperating view (I5d1ac)
  48. @ExperimentalComposeUiApi class ComposeInAndroidCoordinatorLayout : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.compose_in_android_coordinator_layout) findViewById<ComposeView>(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)
  49. <!-- res/layout/android_in_compose_nested_scroll_interop.xml —> <FrameLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/main_list" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>

    <!-- res/layout/android_in_compose_nested_scroll_interop_list_item.xml —> <FrameLayout> <TextView android:id="@+id/list_item" /> </FrameLayout> Link: Nested Scrolling Interop Phase 2: A scrolling view child inside a compose parent (If7949)
  50. 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)
  51. @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)
  52. @Composable private fun NestedScrollInteropComposeParentWithAndroidChild() { ... AndroidView( { context ->

    LayoutInflater.from(context) .inflate(R.layout.android_in_compose_nested_scroll_interop, null).apply { with(findViewById<RecyclerView>(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)
  53. // Compose 1.1 + RecyclerView 1.2 import androidx.compose.ui.platform.ComposeView class MyComposeAdapter

    : RecyclerView.Adapter<MyComposeViewHolder>() { 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
  54. // Compose 1.2 + RecyclerView 1.3 import androidx.compose.ui.platform.ComposeView class MyComposeAdapter

    : RecyclerView.Adapter<MyComposeViewHolder>() { 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 // ) // } }
  55. 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)
  56. + 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)
  57. + 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)
  58. + 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)
  59. 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)
  60. 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
  61. Compose for Wear OS Jetpack Compose for Wear OS Now

    in beta! Link: g.co/wear/compose
  62. 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(...) } ... }
  63. 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(/*...*/) { /*...*/ } } }
  64. Horologist Wear OS ѐߊ੗ܳ ࠁ৮ೞח Ѫਸ ݾ಴۽ ೞח ۄ੉࠳۞ܻ ޘ਺ੑפ׮.

    ѐߊ੗о ੌ߈੸ਵ۽ ೙ਃ۽ ೞ૑݅ 
 ই૒ ࢎਊೡ ࣻ হח ӝמਸ ઁҕ೤פ׮. • Media • Composables • Compose Layout • Audio and UI • Tiles Compose for Wear OS Link: https://github.com/google/horologist
  65. Thank you! 
 Android Developer / NAVER WEBTOON Android GDE

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