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

What's new in Jetpack 2020

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

What's new in Jetpack 2020

한국 개발자를 위한 ANDROID 11 MEETUP 웨비나에서 발표한 자료입니다.
https://developersonair.withgoogle.com/events/a11meetup-korea

Avatar for Sungyong An

Sungyong An

July 30, 2020
Tweet

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. GDG Incheon Hilt: ViewModel class ExampleViewModel( private val repository: ExampleRepository,

    private val savedStateHandle: SavedStateHandle ) : ViewModel() Before
  2. GDG Incheon Hilt: ViewModel class ExampleViewModel @ViewModelInject constructor( private val

    repository: ExampleRepository, @Assisted private val savedStateHandle: SavedStateHandle ) : ViewModel() private val exampleViewModel: ExampleViewModel by viewModels() After
  3. GDG Incheon Hilt: WorkManager class ExampleWorker( appContext: Context, workerParams: WorkerParameters,

    workerDependency: WorkerDependency ) : Worker(appContext, workerParams) { ... } class ExampleApplication : Application(), Configuration.Provider { override fun getWorkManagerConfiguration() = Configuration.Builder() .setWorkerFactory(workerFactory) .build() } Before
  4. GDG Incheon Hilt: WorkManager class ExampleWorker @WorkerInject constructor( @Assisted appContext:

    Context, @Assisted workerParams: WorkerParameters, workerDependency: WorkerDependency ) : Worker(appContext, workerParams) { ... } @HiltAndroidApp class ExampleApplication : Application(), Configuration.Provider { @Inject lateinit var workerFactory: HiltWorkerFactory override fun getWorkManagerConfiguration() = Configuration.Builder() .setWorkerFactory(workerFactory) .build() } After
  5. GDG Incheon dependencies { def hilt_version = "1.0.0-alpha02" // For

    ViewModel implementation "androidx.hilt:hilt-lifecycle-viewmodel:$hilt_version" // For WorkManager implementation "androidx.hilt:hilt-work:$hilt_version" kapt "androidx.hilt:hilt-compiler:$hilt_version" } Hilt Example: https://github.com/Moop-App/Moop-Android/pull/75
  6. GDG Incheon More… ؊ ੗ࣁೠ ղਊ਷ ׮਺ ࣁ࣌ীࢲ ࣗѐؾפ׮. Dagger

    ইצ Hilt۽ Android DI ೞӝ 20:00 - 20:30 ੉थ޹
  7. GDG Incheon CP App Startup Pixel 2 on Android 10

    Cost of an empty Content Provider 50 10 1 0 # of Providers 0 5 10 15 20 Cost (ms) CP CP CP CP
  8. GDG Incheon App Startup class WorkManagerInitializer : Initializer<WorkManager> { override

    fun create(context: Context): WorkManager { val configuration = Configuration.Builder().build() WorkManager.initialize(context, configuration) return WorkManager.getInstance(context) } override fun dependencies(): List<Class<out Initializer<*>>> { return emptyList() } }
  9. GDG Incheon App Startup class WorkManagerInitializer : Initializer<WorkManager> { override

    fun create(context: Context): WorkManager { val configuration = Configuration.Builder().build() WorkManager.initialize(context, configuration) return WorkManager.getInstance(context) } override fun dependencies(): List<Class<out Initializer<*>>> { return listOf(LogInitializer::class.java) } } class LogInitializer : Initializer<Unit> { ... } Dependency ୶о
  10. GDG Incheon App Startup <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <meta-data

    android:name="com.example.LoggerInitializer" android:value="androidx.startup" /> <meta-data android:name="com.example.WorkManagerInitializer" android:value="androidx.startup" /> </provider> Manifest ࢶ঱ Example: https://github.com/Moop-App/Moop-Android/pull/79
  11. GDG Incheon App Startup ० জ ѐߊ੗, ۄ੉࠳۞ܻ ѐߊ੗ ݽف

    ࢎਊоמ ० ૑ো ୡӝച ૑ਗ ० ݽٚ Initializerী Trace ੗ز ୶о Faster application initialization (single ContentProvider)
  12. GDG Incheon Tracing ० AndroidX Coreী ઁҕغ؍ TraceCompatо ܻ࠙ػ ۄ੉࠳۞ܻ

    ० KTXী async ӝמ ୶о ૑ਗ ० Startup, Benchmark ١ীࢲ ࢎਊ New!
  13. GDG Incheon Tracing dependencies { implementation "androidx.tracing:tracing:1.0.0-beta01" } import androidx.tracing.trace

    val result = trace("doSomething") { doSomething() } val result = traceAsync("doSomethingAsync", cookie = 1) { doSomethingAsync() } Coroutine ૑ਗ
  14. GDG Incheon Benchmark ० Allocation ஏ੿ ୶о ० Profiling ૑ਗ

    ० ӝࠄ ജ҃ࢸ੿ ѐࢶ
 (testBuildType, signingConfig.debug) Link: https://github.com/android/performance-samples
  15. GDG Incheon Benchmark: Profiling defaultConfig { testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'none' }

    dependencies { androidTestImplementation 'androidx.benchmark:benchmark-junit4:1.1.0-alpha01' } Disabled
  16. GDG Incheon Benchmark: Profiling adb shell am instrument -w -r

    -e androidx.benchmark.profiling.mode method -e androidx.benchmark.output.enable true -e debug false -e class 'com.example.benchmark.RecyclerViewBenchmark' com.example.benchmark.test/androidx.benchmark.junit4.AndroidBenchmarkRunner ADB
  17. GDG Incheon Benchmark: Profiling defaultConfig { testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'method' testInstrumentationRunnerArgument

    'androidx.benchmark.output.enable', 'true' } Android Studio dependencies { androidTestImplementation 'androidx.benchmark:benchmark-junit4:1.1.0-alpha01' }
  18. GDG Incheon AnimatedVectorDrawable val avd = AnimatedVectorDrawableCompat.create(context, R.drawable.avd) if (avd

    != null) { avd.start() avd.stop() val callback = object: Animatable2Compat.AnimationCallback() { override fun onAnimationStart(drawable: Drawable) {} override fun onAnimationEnd(drawable: Drawable) {} } avd.registerAnimationCallback(callback) avd.unregisterAnimationCallback(callback) } implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
  19. GDG Incheon Seekable AnimatedVectorDrawable val savd = SeekableAnimatedVectorDrawable.create(context, R.drawable.avd) if

    (savd != null) { savd.start() savd.stop() val callback = object : SeekableAnimatedVectorDrawable.AnimationCallback() { override fun onAnimationStart(drawable: SeekableAnimatedVectorDrawable) {} override fun onAnimationPause(drawable: SeekableAnimatedVectorDrawable) {} override fun onAnimationUpdate(drawable: SeekableAnimatedVectorDrawable) {} override fun onAnimationResume(drawable: SeekableAnimatedVectorDrawable) {} override fun onAnimationEnd(drawable: SeekableAnimatedVectorDrawable) {} } savd.registerAnimationCallback(callback) savd.unregisterAnimationCallback(callback) } savd.pause() savd.resume() // seekable savd.currentPlayTime = 100L implementation "androidx.vectordrawable:vectordrawable-seekable:1.0.0-alpha01"
  20. GDG Incheon Core-Animation import android.animation.ValueAnimator val animator = ValueAnimator.ofFloat(0f, 1f)

    // API Level 19 animator.pause() animator.resume() // API Level 22 animator.setCurrentFraction(100f) // API Level 26 animator.currentPlayTime = 100L Platform…
  21. GDG Incheon Core-Animation dependencies { implementation "androidx.core:core-animation:1.0.0-alpha01" } import androidx.core.animation.ValueAnimator

    val animator = ValueAnimator.ofFloat(0f, 1f) animator.pause() animator.resume() animator.setCurrentFraction(100f) animator.currentPlayTime = 100L Compat!
  22. GDG Incheon Core-Animation API 14 ੉റ, ೒ۖಬী ୶оػ Animator API

    ݽفܳ ૑ਗೠ׮. ० Pause / Resume ० Seek (Fraction, Time) ० Testing ૑ਗ (AnimatorTestRule)
  23. GDG Incheon WindowManager import androidx.window.WindowManager val wm = WindowManager(this, null)

    // DeviceState.POSTURE_UNKNOWN // DeviceState.POSTURE_CLOSED // DeviceState.POSTURE_HALF_OPENED // DeviceState.POSTURE_OPENED // DeviceState.POSTURE_FLIPPED val posture: Int = wm.deviceState.posture // Galaxy Z Flip: // [ DisplayFeature{ bounds=Rect(0, 1226 - 1080, 1226), type=FOLD } ] val displayFeatures: List<DisplayFeature> = wm.windowLayoutInfo.displayFeatures ಫ؊࠶ ӝӝ୊ۢ ࢜۽਍ ഋక੄ ױ݈ਸ ૑ਗೞחؘ ب਑ਸ ળ׮.
  24. GDG Incheon WindowManager val wm = WindowManager(this, null) val mainExecutor

    = ContextCompat.getMainExecutor(context) val deviceStateCallback = Consumer<DeviceState> { newDeviceState -> ... } wm.registerDeviceStateChangeCallback(mainExecutor, deviceStateCallback) wm.unregisterDeviceStateChangeCallback(deviceStateCallback) val layoutInfoCallback = Consumer<WindowLayoutInfo> { newLayoutInfo -> ... } wm.registerLayoutChangeCallback(mainExecutor, layoutInfoCallback) wm.unregisterLayoutChangeCallback(layoutInfoCallback) dependencies { implementation "androidx.window:window:1.0.0-alpha01" } Callback
  25. GDG Incheon Repository Paging 3 Link: https://developer.android.com/topic/libraries/architecture/paging/v3-network-db ViewModel UI PagingDataAdapter

    Flow<PagingData> Pager RemoteMediator PagingSource DB Network API۽ ࠛ۞ৡ ؘ੉ఠܳ DBী ੷੢೧فҊ, UIী ಴दೡ ࣻ ੓׮.
  26. GDG Incheon Repository Paging 3 Link: https://developer.android.com/topic/libraries/architecture/paging/v3-paged-data ViewModel UI PagingDataAdapter

    Flow<PagingData> Pager PagingSource Network RemoteMediatorܳ ੉ਊೞৈ, ؘ੉ఠܳ DBী ԙ ੷੢೧ঠ ೞח Ѫ਷ ইפ׮.
  27. GDG Incheon Paging 3 class MyPagingSource : PagingSource<Key, Value>() {

    override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> { try { val result = api.requestPage(params.key) return Page( data = result.items, nextKey = result.nextKey ) } catch(error: IOException) { return Error(error) } } } PagingSource
  28. GDG Incheon Paging 3 dependencies { implementation "androidx.paging:paging-runtime:3.0.0-alpha02" } //

    ViewModel val pagingFlow = Pager(PagingConfig(pageSize = 10)) { MyPagingSource() }.flow.cachedIn(viewModelScope) // Activity recyclerview.adpater = MyPagingAdapter() lifecycleScope.launch { viewModel.pagingFlow.collectLatest { adapter.submitData(it) } } Codelab: https://codelabs.developers.google.com/codelabs/android-paging Pager Paging DataAdapter
  29. GDG Incheon Paging 3 Kotlin Coroutines & Flow Compat with

    Paging 2 Loading State & Retry Headers & Footers
  30. GDG Incheon Paging 3: Migration @Dao interface CheeseDao { @Query("SELECT

    * FROM Cheese ORDER BY name COLLATE NOCASE ASC") fun allCheesesByName(): DataSource.Factory<Int, Cheese> } class CheeseViewModel(private val dao: CheeseDao) : ViewModel() { val allCheeses = dao.allCheesesByName().toLiveData( Config(pageSize = 60, enablePlaceholders = true, maxSize = 200) ) } class CheeseAdapter : PagedListAdapter<Cheese, CheeseViewHolder>(diffCallback) {..} viewModel.allCheeses.observe(this, Observer { adapter.submitList(it) }) 2.0
  31. GDG Incheon Paging 3: Migration Example: https://github.com/android/architecture-components-samples/commit/a2151cf @Dao interface CheeseDao

    { @Query("SELECT * FROM Cheese ORDER BY name COLLATE NOCASE ASC") fun allCheesesByName(): PagingSource<Int, Cheese> } class CheeseViewModel(private val dao: CheeseDao) : ViewModel() { val allCheeses = Pager( PagingConfig(pageSize = 60, enablePlaceholders = true, maxSize = 200) ) { dao.allCheesesByName() }.flow } class CheeseAdapter : PagingDataAdapter<Cheese, CheeseViewHolder>(diffCallback) {..} lifecycleScope.launch { viewModel.allCheeses.collectLatest { adapter.submitData(it) } } 3.0
  32. GDG Incheon Navigation ० Dynamic Feature Module ా೤ ० Navigation

    Testing ० Result ӝמ ० NavigationUIী Openable ૑ਗ ० ٩݂௼ী Action, Mime Type ୶о ૑ਗ
  33. GDG Incheon Navigation: DFM <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment" app:navGraph="@navigation/nav_graph" ... />

    <navigation> <fragment android:id="@+id/theater_map" android:name="soup.movie.theatermap.TheaterMapFragment" app:moduleName="theatermap" /> </navigation> Link: https://developer.android.com/guide/navigation/navigation-dynamic
  34. GDG Incheon Navigation: DFM <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment" app:navGraph="@navigation/nav_graph" ... />

    <navigation app:progressDestination="@id/progress"> <fragment android:id="@+id/theater_map" android:name="soup.movie.theatermap.TheaterMapFragment" app:moduleName="theatermap" /> <fragment android:id="@+id/progress" android:name="soup.movie.ui.CustomProgressFragment" /> </navigation> Example: https://github.com/Moop-App/Moop-Android/commit/49ba747c Custom Progress
  35. GDG Incheon Navigation: Result // Listen a result override fun

    onViewCreated(view: View, savedInstanceState: Bundle?) { findNavController().currentBackStackEntry ?.savedStateHandle ?.getLiveData<String>("key") ?.observe(viewLifecycleOwner) { result -> ... } } // Set a result to previous destination findNavController().previousBackStackEntry?.savedStateHandle?.set("key", result) Link: https://developer.android.com/guide/navigation/navigation-programmatic#returning_a_result SharedViewModel ࢎਊਸ ؊ ӂ੢ೠ׮.
  36. GDG Incheon Navigation: Deeplink val request = NavDeepLinkRequest.Builder .fromUri("android-app://androidx.navigation.app/profile".toUri()) .setAction(Intent.ACTION_SEND)

    .setMimeType("image/*") .build() findNavController().navigate(request) dependencies { def nav_version = "2.3.0" api "androidx.navigation:navigation-fragment-ktx:$nav_version" api "androidx.navigation:navigation-ui-ktx:$nav_version" api "androidx.navigation:navigation-dynamic-features-fragment:$nav_version" }
  37. GDG Incheon val requestContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> if

    (uri != null) { // do something } } requestContent.launch("image/*") GetMultipleContents() TakePicture() StartActivityForResult() StartIntentSenderForResult() more... Results & Permissions startActivityForResultܳ ؀୓ೞח ࢜۽਍ API
  38. GDG Incheon val requestPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> if

    (granted) { // do something } } requestPermission.launch(Manifest.permission.ACCESS_FINE_LOCATION) dependencies { implementation "androidx.activity:activity:1.2.0-alpha06" implementation "androidx.fragment:fragment:1.3.0-alpha06" } Results & Permissions ӂജਸ ദٙೞח ߑߨب ખ ؊ एਕ૓׮
  39. GDG Incheon WorkManager ० Long-running Worker ૑ਗ ० Progress ૑ਗ

    ० Lint Rules ୶о ० In-process झா઴۞ ѐࢶ Worker Background
  40. GDG Incheon Foreground Worker class ForegroundWorker(...) : CoroutineWorker(context, parameters) {

    override suspend fun doWork(): Result { setForeground(createForegroundInfo()) // do long-running tasks return Result.success() } private fun createForegroundInfo(): ForegroundInfo { val notification = NotificationCompat.Builder(...) .addAction(R.drawable.icon, "Cancel", WorkManager.getInstance(applicationContext) .createCancelPendingIntent(id)) .build() return ForegroundInfo(0, notification) } } Notification ID Worker ID Link: https://developer.android.com/topic/libraries/architecture/workmanager/advanced/long-running Foreground Service ੉ਊ
  41. GDG Incheon Worker Progress class ProgressWorker(...) : CoroutineWorker(context, parameters) {

    override suspend fun doWork(): Result { setProgress(workDataOf(PROGRESS to 0)) ... setProgress(workDataOf(PROGRESS to 50)) ... setProgress(workDataOf(PROGRESS to 100)) return Result.success() } companion object { const val PROGRESS = "Progress" } } Link: https://developer.android.com/topic/libraries/architecture/workmanager/intermediate-progress
  42. GDG Incheon Worker Progress val request = OneTimeWorkRequestBuilder<ProgressWorker>().build() WorkManager.getInstance(this) .getWorkInfoByIdLiveData(request.id)

    .observe(this, Observer { workInfo: WorkInfo? -> if (workInfo != null) { val progress = workInfo.progress val value = progress.getInt(PROGRESS, 0) // Do something with progress information } }) RUNNING Stateীࢲ݅ ࢎਊೞח Ѫਸ ӂ੢ Link: https://developer.android.com/topic/libraries/architecture/workmanager/intermediate-progress
  43. GDG Incheon AppCompat ० Configuration overrides API ० More reliable

    Dark Theme ० ViewTree*Owner ० API for inserting rich content
  44. GDG Incheon AppCompat: Configuration dependencies { implementation "androidx.appcompat:appcompat:1.2.0-rc02" } override

    fun attachBaseContext(context: Context) { val config = new Configuration() config.locale = Locale.KOREA config.fontScale = 0f val updatedContext = context.createConfigurationContext(config) super.attachBaseContext(updatedContext) } 1.1.0 ੉ग ࣻ੿
  45. GDG Incheon AppCompat: ViewTree*Owner dependencies { implementation "androidx.appcompat:appcompat:1.3.0-alpha01" } class

    ViewHolder(view: View) : RecyclerView.ViewHolder(view) { init { val lifecycleOwner = ViewTreeLifecycleOwner.get(view) val owner = ViewTreeViewModelStoreOwner.get(view) if (lifecycleOwner != null && owner != null) { val viewModel: MyViewModel = ViewModelProvider(owner).get() viewModel.state.observe(lifecycleOwner, Observer { // Anyway, this is actually possible! }) } } }
  46. GDG Incheon AppCompat: Rich Content appCompatEditText.richContentReceiverCompat = object : TextViewRichContentReceiverCompat()

    { override fun getSupportedMimeTypes() = setOf("text/*", "image/*") override fun onReceive(textview: TextView, clip: ClipData, source: Int, flags: Int): Boolean { if (clip.description.hasMimeType("image/*")) { // Handle image clip data! } return super.onReceive(textview, clip, source, flags) } } dependencies { implementation "androidx.appcompat:appcompat:1.3.0-alpha01" }
  47. GDG Incheon Webkit val webView: WebView = ... if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK))

    { WebSettingsCompat.setForceDark( webView.settings, WebSettingsCompat.FORCE_DARK_ON) } webView.loadUrl("https://www.google.com") dependencies { implementation "androidx.webkit:webkit:1.2.0" } AUTO / ON / OFF
  48. GDG Incheon dependencies { implementation "androidx.recyclerview:recyclerview:1.2.0-alpha04" } val adapter1: MyAdapter

    = ... val adapter2: AnotherAdapter = ... val concatAdapter = ConcatAdapter(adapter1, adapter2) recyclerView.setAdapter(concatAdapter) adapter.stateRestorationPolicy = StateRestorationPolicy.ALLOW StateRestorationPolicy.PREVENT_WHEN_EMPTY StateRestorationPolicy.PREVENT ConcatAdapter Lazy state restoration RecyclerView
  49. GDG Incheon val callback = object : FragmentTransactionCallback() { override

    fun onFragmentPreAdded(fragment: Fragment) = OnPostEventListener { ... } override fun onFragmentPreRemoved(fragment: Fragment) = OnPostEventListener { ... } override fun onFragmentMaxLifecyclePreUpdated( fragment: Fragment, maxLifecycleState: Lifecycle.State ): OnPostEventListener { // On pre-event return OnPostEventListener { // On post-event } } } ViewPager2 dependencies { implementation "androidx.viewpager2:viewpager2:1.1.0-alpha01" } val pagerAdapter = object : FragmentStateAdapter(this) { ... } pagerAdapter.registerFragmentTransactionCallback(callback) pagerAdapter.unregisterFragmentTransactionCallback(callback) FragmentTransactionCallback
  50. GDG Incheon <androidx.camera.view.PreviewView android:id="@+id/previewView" /> val cameraProviderFuture = ProcessCameraProvider.getInstance(context) cameraProviderFuture.addListener(Runnable

    { val cameraSelector: CameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build() val preview: Preview = Preview.Builder().build() preview.setSurfaceProvider(previewView.createSurfaceProvider()) val cameraProvider = cameraProviderFuture.get() cameraProvider.bindToLifecycle(this, cameraSelector, preview) }, ContextCompat.getMainExecutor(context)) CameraX: PreviewView Surface / Texture ૑ਗ Link: https://developer.android.com/training/camerax/preview
  51. GDG Incheon Security lollipop ० Lollipop ૑ਗ (API level 21+)

    ० MasterKeys -> MasterKey Link: https://developer.android.com/jetpack/androidx/releases/security#security-crypto-1.1.0-alpha01
  52. GDG Incheon Security val keyAlias: String = MasterKeys .getOrCreate(MasterKeys.AES256_GCM_SPEC) val

    sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create( "shared_prefs", keyAlias, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM) Before
  53. GDG Incheon Security dependencies { implementation "androidx.security:security-crypto:1.1.0-alpha01" } val masterKey:

    MasterKey = MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create( context, "shared_prefs", masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM) After
  54. GDG Incheon and… Compose! ० View interoperability ० More Material

    UI components ० Dark theme ૑ਗ ० ConstraintLayout ૑ਗ
 
 …