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

What's new in Jetpack 2020

What's new in Jetpack 2020

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

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 ૑ਗ
 
 …